Subversion Repositories SmartDukaan

Rev

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

Rev 36670 Rev 36681
Line 56... Line 56...
56
	state.authUserId = parseInt(uid);
56
	state.authUserId = parseInt(uid);
57
	state.categoryId = parseInt($('#bp-category').val());
57
	state.categoryId = parseInt($('#bp-category').val());
58
    state.mode = 'list'; // 'list' or 'create' or 'view'
58
    state.mode = 'list'; // 'list' or 'create' or 'view'
59
	$('#bp-user-label').text($('#bp-auth-user option:selected').text());
59
	$('#bp-user-label').text($('#bp-auth-user option:selected').text());
60
 
60
 
-
 
61
	// Load all saved base locations — default goes in state.home*, the rest is
61
    // Load home location
62
	// exposed via a picker in the route panel so the planner can pick a
-
 
63
	// different starting point for this one beat without changing the default.
62
	$.ajax({
64
	$.ajax({
63
		url: context + "/beatPlan/getBaseLocation", type: "GET", dataType: "json",
65
		url: context + "/beatPlan/listBaseLocations", type: "GET", dataType: "json",
64
		data: {authUserId: uid},
66
		data: {authUserId: uid},
65
		success: function (r) {
67
		success: function (r) {
66
			var data = r.response || r;
68
			var data = r.response || r;
-
 
69
			state.baseLocations = data.locations || [];
-
 
70
			var def = state.baseLocations.find(function (l) {
-
 
71
				return l.isDefault;
67
			if (data.locationName) {
72
			}) || state.baseLocations[0];
-
 
73
			if (def) {
68
				state.homeLat = parseFloat(data.latitude);
74
				state.homeLat = parseFloat(def.latitude);
69
				state.homeLng = parseFloat(data.longitude);
75
				state.homeLng = parseFloat(def.longitude);
70
				state.homeName = data.locationName;
76
				state.homeName = def.locationName;
-
 
77
				state.activeBaseLocationId = def.id;
-
 
78
			} else {
-
 
79
				state.activeBaseLocationId = null;
71
            }
80
            }
72
            showBeatList();
81
            showBeatList();
73
        }
82
        }
74
    });
83
    });
75
});
84
});
76
 
85
 
-
 
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.
-
 
88
function renderBaseLocationPicker() {
-
 
89
	var locs = state.baseLocations || [];
-
 
90
	if (locs.length === 0) return '';
-
 
91
	var activeId = state.activeBaseLocationId;
-
 
92
	if (locs.length === 1) {
-
 
93
		var only = locs[0];
-
 
94
		return '<div style="font-size:11px;color:#94a3b8;margin-bottom:10px;">'
-
 
95
			+ 'Starting from: <strong style="color:#e2e8f0;">' + (only.locationName || 'Home') + '</strong>'
-
 
96
			+ (only.isDefault ? ' <span style="color:#22c55e;">(default)</span>' : '')
-
 
97
			+ '</div>';
-
 
98
	}
-
 
99
	var html = '<div style="margin-bottom:10px;">'
-
 
100
		+ '<label style="font-size:11px;color:#94a3b8;display:block;margin-bottom:4px;">Start this beat from:</label>'
-
 
101
		+ '<select id="bp-base-picker" style="width:100%;padding:6px 8px;border:1px solid #475569;border-radius:4px;background:#0f172a;color:#e2e8f0;font-size:12px;font-family:inherit;">';
-
 
102
	locs.forEach(function (l) {
-
 
103
		var sel = (l.id === activeId) ? ' selected' : '';
-
 
104
		var label = (l.locationName || 'Location ' + l.id) + (l.isDefault ? ' (default)' : '');
-
 
105
		html += '<option value="' + l.id + '"' + sel + '>' + label + '</option>';
-
 
106
	});
-
 
107
	html += '</select></div>';
-
 
108
	return html;
-
 
109
}
-
 
110
 
-
 
111
$(document).on('change', '#bp-base-picker', function () {
-
 
112
	var id = parseInt($(this).val());
-
 
113
	if (!id) return;
-
 
114
	applyBaseLocation(id);
-
 
115
});
-
 
116
 
-
 
117
function applyBaseLocation(id) {
-
 
118
	var loc = (state.baseLocations || []).find(function (l) {
-
 
119
		return l.id === id;
-
 
120
	});
-
 
121
	if (!loc) return;
-
 
122
	state.homeLat = parseFloat(loc.latitude);
-
 
123
	state.homeLng = parseFloat(loc.longitude);
-
 
124
	state.homeName = loc.locationName;
-
 
125
	state.activeBaseLocationId = id;
-
 
126
 
-
 
127
	// Only day 1 starts from the chosen base; later days start where the
-
 
128
	// previous day ended (existing behavior unchanged).
-
 
129
	if (state.days && state.days.length > 0) {
-
 
130
		state.days[0].startLat = state.homeLat;
-
 
131
		state.days[0].startLng = state.homeLng;
-
 
132
		state.days[0].startName = state.homeName;
-
 
133
		if (typeof recalcDay === 'function') {
-
 
134
			var saved = state.currentDay;
-
 
135
			state.currentDay = 1;
-
 
136
			recalcDay();
-
 
137
			state.currentDay = saved;
-
 
138
		}
-
 
139
	}
-
 
140
 
-
 
141
	// Move the home marker on the map + recenter so the user actually sees it.
-
 
142
	if (typeof google !== 'undefined' && google.maps && map) {
-
 
143
		var pos = new google.maps.LatLng(state.homeLat, state.homeLng);
-
 
144
		if (homeMarker) {
-
 
145
			homeMarker.setPosition(pos);
-
 
146
			homeMarker.setTitle('Home: ' + state.homeName);
-
 
147
		} else {
-
 
148
			homeMarker = new google.maps.Marker({
-
 
149
				map: map, position: pos, title: 'Home: ' + state.homeName,
-
 
150
				icon: {
-
 
151
					url: 'https://maps.google.com/mapfiles/kml/shapes/homegardenbusiness.png',
-
 
152
					scaledSize: new google.maps.Size(32, 32)
-
 
153
				},
-
 
154
				zIndex: 100
-
 
155
			});
-
 
156
		}
-
 
157
		map.panTo(pos);
-
 
158
	}
-
 
159
 
-
 
160
	if (typeof drawRoute === 'function') drawRoute();
-
 
161
	if (typeof renderRoutePanel === 'function') renderRoutePanel();
-
 
162
	if (typeof updateBottomBar === 'function') updateBottomBar();
-
 
163
}
-
 
164
 
77
// ============ BEAT LIST VIEW ============
165
// ============ BEAT LIST VIEW ============
78
function showBeatList() {
166
function showBeatList() {
79
    state.mode = 'list';
167
    state.mode = 'list';
80
    $('#bottom-bar').hide();
168
    $('#bottom-bar').hide();
81
 
169
 
Line 248... Line 336...
248
									totalKm: 0, totalMins: 0
336
									totalKm: 0, totalMins: 0
249
								});
337
								});
250
							});
338
							});
251
							state.currentDay = 1;
339
							state.currentDay = 1;
252
 
340
 
-
 
341
							// Snapshot the originals — used during save to detect day-count
-
 
342
							// increases (blocked) and lead removals (popup with cancel/reschedule).
-
 
343
							state.originalDayCount = state.days.length;
-
 
344
							state.originalLeads = [];
-
 
345
							state.days.forEach(function (d) {
-
 
346
								d.visits.forEach(function (v) {
-
 
347
									if (v.type === 'lead' || v.isLead) {
-
 
348
										state.originalLeads.push({
-
 
349
											leadId: v.id,
-
 
350
											dayNumber: d.dayNumber,
-
 
351
											name: v.name || ('Lead #' + v.id)
-
 
352
										});
-
 
353
									}
-
 
354
								});
-
 
355
							});
-
 
356
 
253
							// Async enrich each lead with name + geo
357
							// Async enrich each lead with name + geo
254
							var leadPromises = [];
358
							var leadPromises = [];
255
							state.days.forEach(function (day) {
359
							state.days.forEach(function (day) {
256
								day.visits.forEach(function (v) {
360
								day.visits.forEach(function (v) {
257
									if (!v.isLead) return;
361
									if (!v.isLead) return;
Line 263... Line 367...
263
												v.lng = g.longitude;
367
												v.lng = g.longitude;
264
											}
368
											}
265
										}),
369
										}),
266
										$.get(context + '/getLead?leadId=' + v.id).then(function (rr) {
370
										$.get(context + '/getLead?leadId=' + v.id).then(function (rr) {
267
											var l = rr.response || rr;
371
											var l = rr.response || rr;
-
 
372
											if (l && l.firstName) {
268
											if (l && l.firstName) v.name = 'LEAD - ' + l.firstName + ' ' + (l.lastName || '');
373
												var nm = 'LEAD - ' + l.firstName + ' ' + (l.lastName || '');
-
 
374
												v.name = nm;
-
 
375
												// Keep the originalLeads snapshot label in sync so the
-
 
376
												// removed-leads popup shows the real name, not 'LEAD #123'.
-
 
377
												(state.originalLeads || []).forEach(function (ol) {
-
 
378
													if (ol.leadId === v.id) ol.name = nm;
-
 
379
												});
-
 
380
											}
269
										})
381
										})
270
									);
382
									);
271
								});
383
								});
272
							});
384
							});
273
 
385
 
274
							$.when.apply($, leadPromises).always(function () {
386
							$.when.apply($, leadPromises).always(function () {
-
 
387
								// Pre-select whichever saved base location matches the beat's
-
 
388
								// existing day-1 start (lat/lng), so the picker reflects current state.
-
 
389
								var day1 = state.days[0];
-
 
390
								if (day1 && state.baseLocations) {
-
 
391
									var match = state.baseLocations.find(function (l) {
-
 
392
										return Math.abs(parseFloat(l.latitude) - day1.startLat) < 1e-4
-
 
393
											&& Math.abs(parseFloat(l.longitude) - day1.startLng) < 1e-4;
-
 
394
									});
-
 
395
									if (match) state.activeBaseLocationId = match.id;
-
 
396
								}
-
 
397
 
275
								renderRoutePanel();
398
								renderRoutePanel();
276
								var dateLabel = planDate
399
								var dateLabel = planDate
277
									? '<div style="font-size:11px;color:#f59e0b;margin-bottom:8px;">Editing run on: ' + planDate + ' (leads on this date can be removed)</div>'
400
									? '<div style="font-size:11px;color:#f59e0b;margin-bottom:8px;">Editing run on: ' + planDate + ' (leads on this date can be removed)</div>'
278
									: '<div style="font-size:11px;color:#94a3b8;margin-bottom:8px;">Editing beat template (partners only)</div>';
401
									: '<div style="font-size:11px;color:#94a3b8;margin-bottom:8px;">Editing beat template (partners only)</div>';
279
								$('#route-list').prepend(
402
								$('#route-list').prepend(
280
									'<div style="margin-bottom:8px;"><button class="btn-back-to-list" '
403
									'<div style="margin-bottom:8px;"><button class="btn-back-to-list" '
281
									+ 'style="font-size:11px;padding:4px 10px;border:1px solid #475569;'
404
									+ 'style="font-size:11px;padding:4px 10px;border:1px solid #475569;'
282
									+ 'border-radius:4px;background:none;color:#94a3b8;cursor:pointer;'
405
									+ 'border-radius:4px;background:none;color:#94a3b8;cursor:pointer;'
283
									+ 'font-family:inherit;">← Back to list</button></div>'
406
									+ 'font-family:inherit;">← Back to list</button></div>'
284
									+ '<div style="font-size:14px;font-weight:600;color:#f59e0b;margin-bottom:4px;">'
407
									+ '<div style="font-size:14px;font-weight:600;color:#f59e0b;margin-bottom:4px;">'
285
									+ 'EDITING: ' + state.beatTitle + '</div>' + dateLabel);
408
									+ 'EDITING: ' + state.beatTitle + '</div>' + dateLabel
-
 
409
									+ renderBaseLocationPicker());
286
 
410
 
287
								initMap();
411
								initMap();
288
								// Add lead markers (orange) on the map
412
								// Add lead markers (orange) on the map
289
								state.days.forEach(function (day) {
413
								state.days.forEach(function (day) {
290
									day.visits.forEach(function (v) {
414
									day.visits.forEach(function (v) {
Line 982... Line 1106...
982
function renderRoutePanel() {
1106
function renderRoutePanel() {
983
	var html = '';
1107
	var html = '';
984
    if (state.mode === 'create' && state.beatTitle) {
1108
    if (state.mode === 'create' && state.beatTitle) {
985
        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>';
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>';
986
        html += '<div style="font-size:14px;font-weight:600;color:#60a5fa;margin-bottom:10px;">' + state.beatTitle + '</div>';
1110
        html += '<div style="font-size:14px;font-weight:600;color:#60a5fa;margin-bottom:10px;">' + state.beatTitle + '</div>';
-
 
1111
		html += renderBaseLocationPicker();
987
    }
1112
    }
988
	state.days.forEach(function (day) {
1113
	state.days.forEach(function (day) {
989
		var isCurrent = day.dayNumber === state.currentDay;
1114
		var isCurrent = day.dayNumber === state.currentDay;
990
        var isLastDay = day.dayNumber === state.days.length;
1115
        var isLastDay = day.dayNumber === state.days.length;
991
		html += '<div class="route-day">';
1116
		html += '<div class="route-day">';
Line 1184... Line 1309...
1184
	})) {
1309
	})) {
1185
		alert('No visits added');
1310
		alert('No visits added');
1186
		return;
1311
		return;
1187
	}
1312
	}
1188
 
1313
 
1189
    var beatTitle = state.beatTitle || 'Beat';
1314
	var beatTitle = state.beatTitle || 'Beat';
-
 
1315
	var isEdit = state.mode === 'edit' && state.editingBeatId;
-
 
1316
 
-
 
1317
	// EDIT RULE: cannot grow day count. Caught client-side so the modal
-
 
1318
	// doesn't even open; server enforces the same rule.
-
 
1319
	if (isEdit && state.originalDayCount && state.days.length > state.originalDayCount) {
-
 
1320
		alert('Cannot increase the number of days while editing.\n\n'
-
 
1321
			+ 'Original: ' + state.originalDayCount + ' day(s)\n'
-
 
1322
			+ 'Current:  ' + state.days.length + ' day(s)\n\n'
-
 
1323
			+ 'Please create a new beat for additional days.');
-
 
1324
		return;
-
 
1325
	}
1190
 
1326
 
1191
	// Build plan data and save to DB first
1327
	// Build plan data
1192
	var daysData = [];
1328
	var daysData = [];
1193
	state.days.forEach(function (day) {
1329
	state.days.forEach(function (day) {
1194
		if (day.visits.length === 0) return;
1330
		if (day.visits.length === 0) return;
1195
		daysData.push({
1331
		daysData.push({
1196
			dayNumber: day.dayNumber,
1332
			dayNumber: day.dayNumber,
Line 1207... Line 1343...
1207
				return {id: v.id, type: v.type || 'partner'};
1343
				return {id: v.id, type: v.type || 'partner'};
1208
			})
1344
			})
1209
		});
1345
		});
1210
	});
1346
	});
1211
 
1347
 
1212
	// Dates will be assigned later via calendar — pass nulls for now
1348
	// EDIT RULE: detect removed leads → show popup with cancel/reschedule per lead
-
 
1349
	var removedLeads = [];
-
 
1350
	if (isEdit && state.originalLeads && state.originalLeads.length) {
-
 
1351
		var currentLeadIds = {};
-
 
1352
		state.days.forEach(function (d) {
-
 
1353
			d.visits.forEach(function (v) {
-
 
1354
				if (v.type === 'lead' || v.isLead) currentLeadIds[v.id] = true;
-
 
1355
			});
-
 
1356
		});
-
 
1357
		removedLeads = state.originalLeads.filter(function (l) {
-
 
1358
			return !currentLeadIds[l.leadId];
-
 
1359
		});
-
 
1360
	}
-
 
1361
 
-
 
1362
	if (removedLeads.length > 0) {
-
 
1363
		openRemovedLeadsModal(removedLeads, function (actions) {
-
 
1364
			doFinishSubmit(daysData, beatTitle, isEdit, actions);
-
 
1365
		});
-
 
1366
		return;
-
 
1367
	}
-
 
1368
 
-
 
1369
	doFinishSubmit(daysData, beatTitle, isEdit, null);
-
 
1370
});
-
 
1371
 
-
 
1372
function doFinishSubmit(daysData, beatTitle, isEdit, removedLeadActions) {
-
 
1373
	var planPayload = {
-
 
1374
		days: daysData,
1213
	var dates = daysData.map(function () {
1375
		dates: daysData.map(function () {
1214
		return null;
1376
			return null;
1215
	});
1377
		}),
1216
	var planPayload = {days: daysData, dates: dates, beatName: beatTitle.trim()};
1378
		beatName: beatTitle.trim()
-
 
1379
	};
1217
	// When editing a specific scheduled date, pass it so leads removed from that date get cancelled
1380
	if (state.mode === 'edit' && state.editingDate) planPayload.planDate = state.editingDate;
1218
	if (state.mode === 'edit' && state.editingDate) {
1381
	if (removedLeadActions && removedLeadActions.length) {
1219
		planPayload.planDate = state.editingDate;
1382
		planPayload.removedLeadActions = JSON.stringify(removedLeadActions);
1220
	}
1383
	}
1221
	var planData = JSON.stringify(planPayload);
1384
	var planData = JSON.stringify(planPayload);
1222
 
1385
 
1223
	isSubmittingBeat = true;
1386
	isSubmittingBeat = true;
1224
	var isEdit = state.mode === 'edit' && state.editingBeatId;
-
 
1225
	$('#btn-finish').prop('disabled', true).text(isEdit ? 'Updating...' : 'Saving...');
1387
	$('#btn-finish').prop('disabled', true).text(isEdit ? 'Updating...' : 'Saving...');
1226
 
1388
 
1227
	var url = isEdit ? '/beatPlan/updateBeat' : '/beatPlan/submitPlan';
1389
	var url = isEdit ? '/beatPlan/updateBeat' : '/beatPlan/submitPlan';
1228
	var ajaxData = isEdit
1390
	var ajaxData = isEdit
1229
		? {beatId: state.editingBeatId, planData: planData}
1391
		? {beatId: state.editingBeatId, planData: planData}
Line 1236... Line 1398...
1236
		success: function (response) {
1398
		success: function (response) {
1237
			var data = (typeof response === 'string' ? JSON.parse(response) : response);
1399
			var data = (typeof response === 'string' ? JSON.parse(response) : response);
1238
			var result = data.response || data;
1400
			var result = data.response || data;
1239
 
1401
 
1240
			if (result.status) {
1402
			if (result.status) {
1241
				// Mark as saved — finish button stays disabled, no re-submit possible
-
 
1242
				state.savedPlanGroupId = result.planGroupId;
1403
				state.savedPlanGroupId = result.planGroupId;
1243
				$('#bottom-bar').hide();
1404
				$('#bottom-bar').hide();
1244
 
1405
 
1245
                if (result.duplicate) {
1406
				if (result.duplicate) {
1246
					alert('This beat already exists.');
1407
					alert('This beat already exists.');
1247
				} else if (isEdit) {
1408
				} else if (isEdit) {
-
 
1409
					var extras = [];
-
 
1410
					if (result.leadsCancelled) extras.push(result.leadsCancelled + ' lead(s) cancelled');
-
 
1411
					if (result.leadsRescheduled) extras.push(result.leadsRescheduled + ' lead(s) rescheduled');
1248
					alert('Beat "' + beatTitle + '" updated successfully!');
1412
					alert('Beat "' + beatTitle + '" updated successfully'
-
 
1413
						+ (extras.length ? '\n\n' + extras.join('\n') : '') + '.');
1249
                } else {
1414
				} else {
1250
                    alert('Beat "' + beatTitle + '" saved successfully!');
1415
					alert('Beat "' + beatTitle + '" saved successfully!');
1251
                }
1416
				}
1252
 
1417
 
1253
				// Reset edit state, go back to beat list
-
 
1254
				state.editingBeatId = null;
1418
				state.editingBeatId = null;
1255
				state.mode = 'list';
1419
				state.mode = 'list';
1256
                showBeatList();
1420
				showBeatList();
1257
			} else {
1421
			} else {
1258
				$('#btn-finish').prop('disabled', false).text(isEdit ? 'Save Changes' : 'Finish Plan');
1422
				$('#btn-finish').prop('disabled', false).text(isEdit ? 'Save Changes' : 'Finish Plan');
1259
				alert('Error saving beat plan');
1423
				alert('Error saving beat plan');
1260
			}
1424
			}
1261
			isSubmittingBeat = false;
1425
			isSubmittingBeat = false;
1262
		},
1426
		},
1263
		error: function (xhr) {
1427
		error: function (xhr) {
1264
			isSubmittingBeat = false;
1428
			isSubmittingBeat = false;
1265
			$('#btn-finish').prop('disabled', false).text(isEdit ? 'Save Changes' : 'Finish Plan');
1429
			$('#btn-finish').prop('disabled', false).text(isEdit ? 'Save Changes' : 'Finish Plan');
-
 
1430
			// Surface a clean server message if present (e.g., day-increase block, missing beat on reschedule date)
1266
			alert('Error: ' + (xhr.responseText || xhr.statusText));
1431
			var msg = xhr.responseText || xhr.statusText;
-
 
1432
			try {
-
 
1433
				var err = JSON.parse(xhr.responseText);
-
 
1434
				if (err && err.response) {
-
 
1435
					if (typeof err.response === 'string') msg = err.response;
-
 
1436
					else if (err.response.message) msg = err.response.message;
-
 
1437
				}
-
 
1438
			} catch (_) {
-
 
1439
			}
-
 
1440
			alert(msg);
1267
		}
1441
		}
1268
	});
1442
	});
-
 
1443
}
-
 
1444
 
-
 
1445
// ============ REMOVED LEADS POPUP ============
-
 
1446
// Shown when an edit removes one or more leads. Per lead, the user picks:
-
 
1447
//   - Cancel  → mark CANCELLED
-
 
1448
//   - Reschedule + date → move to whichever beat the same user has on that date
-
 
1449
// The date input is validated against /beatPlan/userBeatsOnDate so the user
-
 
1450
// sees "No beat on this date — pick another" before submit.
-
 
1451
function openRemovedLeadsModal(removedLeads, onConfirm) {
-
 
1452
	$('#modal-removed-leads').remove(); // clean any prior instance
-
 
1453
 
-
 
1454
	var rowsHtml = removedLeads.map(function (l) {
-
 
1455
		return ''
-
 
1456
			+ '<div class="rl-row" data-leadid="' + l.leadId + '" style="border:1px solid #334155;border-radius:6px;padding:8px;margin-bottom:8px;">'
-
 
1457
			+ '  <div style="font-weight:600;color:#f1f5f9;margin-bottom:6px;">'
-
 
1458
			+ '    ' + (l.name || ('Lead #' + l.leadId)) + ' <span style="color:#64748b;font-weight:400;font-size:11px;">(was on day ' + l.dayNumber + ')</span>'
-
 
1459
			+ '  </div>'
-
 
1460
			+ '  <label style="display:inline-flex;align-items:center;gap:6px;margin-right:14px;cursor:pointer;font-size:12px;">'
-
 
1461
			+ '    <input type="radio" name="rl-act-' + l.leadId + '" value="cancel" checked> Cancel this lead'
-
 
1462
			+ '  </label>'
-
 
1463
			+ '  <label style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;">'
-
 
1464
			+ '    <input type="radio" name="rl-act-' + l.leadId + '" value="reschedule"> Reschedule to another date'
-
 
1465
			+ '  </label>'
-
 
1466
			+ '  <div class="rl-date-wrap" style="display:none;margin-top:6px;">'
-
 
1467
			+ '    <input type="date" class="rl-date" min="' + fmtDate(new Date()) + '" style="width:auto;display:inline-block;margin-bottom:0;">'
-
 
1468
			+ '    <span class="rl-date-status" style="font-size:11px;margin-left:8px;"></span>'
-
 
1469
			+ '  </div>'
-
 
1470
			+ '</div>';
-
 
1471
	}).join('');
-
 
1472
 
-
 
1473
	var html = ''
-
 
1474
		+ '<div class="modal-overlay show" id="modal-removed-leads">'
-
 
1475
		+ '  <div class="modal-box" style="min-width:520px;max-width:680px;">'
-
 
1476
		+ '    <h3>You removed ' + removedLeads.length + ' lead(s) from this beat</h3>'
-
 
1477
		+ '    <p style="font-size:12px;color:#94a3b8;margin-bottom:12px;">'
-
 
1478
		+ '      Choose what to do with each lead before saving. Reschedule moves the lead to whichever beat this user has on the selected date.'
-
 
1479
		+ '    </p>'
-
 
1480
		+ '    <div id="rl-list" style="max-height:55vh;overflow-y:auto;">' + rowsHtml + '</div>'
-
 
1481
		+ '    <div class="modal-actions">'
-
 
1482
		+ '      <button class="modal-cancel" id="rl-cancel">Cancel (keep editing)</button>'
-
 
1483
		+ '      <button class="modal-confirm" id="rl-confirm">Save with these actions</button>'
-
 
1484
		+ '    </div>'
-
 
1485
		+ '  </div>'
-
 
1486
		+ '</div>';
-
 
1487
	$('body').append(html);
-
 
1488
 
-
 
1489
	// Show/hide date picker per row
-
 
1490
	$('#modal-removed-leads').on('change', 'input[type="radio"]', function () {
-
 
1491
		var $row = $(this).closest('.rl-row');
-
 
1492
		var isResched = $row.find('input[type="radio"]:checked').val() === 'reschedule';
-
 
1493
		$row.find('.rl-date-wrap').toggle(isResched);
-
 
1494
		$row.find('.rl-date-status').text('');
-
 
1495
	});
-
 
1496
 
-
 
1497
	// Validate the date as the user types it — show "no beat on this date" warning
-
 
1498
	$('#modal-removed-leads').on('change', '.rl-date', function () {
-
 
1499
		var $row = $(this).closest('.rl-row');
-
 
1500
		var $status = $row.find('.rl-date-status');
-
 
1501
		var d = $(this).val();
-
 
1502
		if (!d) {
-
 
1503
			$status.text('');
-
 
1504
			return;
-
 
1505
		}
-
 
1506
		$status.text('Checking...').css('color', '#94a3b8');
-
 
1507
		$.ajax({
-
 
1508
			url: context + '/beatPlan/userBeatsOnDate', type: 'GET', dataType: 'json',
-
 
1509
			data: {authUserId: state.authUserId, date: d},
-
 
1510
			success: function (r) {
-
 
1511
				var dd = r.response || r;
-
 
1512
				var beats = dd.beats || [];
-
 
1513
				if (beats.length === 0) {
-
 
1514
					$status.text('No beat on this date — pick another date, or Cancel this lead instead.').css('color', '#ef4444');
-
 
1515
				} else {
-
 
1516
					var names = beats.map(function (b) {
-
 
1517
						return '"' + b.beatName + '" (day ' + b.dayNumber + ')';
-
 
1518
					}).join(', ');
-
 
1519
					$status.text('Will attach to: ' + names).css('color', '#22c55e');
-
 
1520
				}
-
 
1521
			},
-
 
1522
			error: function () {
-
 
1523
				$status.text('Could not verify date').css('color', '#f59e0b');
-
 
1524
			}
-
 
1525
		});
-
 
1526
	});
-
 
1527
 
-
 
1528
	$('#modal-removed-leads').on('click', '#rl-cancel', function () {
-
 
1529
		$('#modal-removed-leads').remove();
-
 
1530
		// abort save — user goes back to editing
-
 
1531
	});
-
 
1532
 
-
 
1533
	$('#modal-removed-leads').on('click', '#rl-confirm', function () {
-
 
1534
		var actions = [];
-
 
1535
		var problems = [];
-
 
1536
		$('#modal-removed-leads .rl-row').each(function () {
-
 
1537
			var $row = $(this);
-
 
1538
			var leadId = parseInt($row.data('leadid'));
-
 
1539
			var mode = $row.find('input[type="radio"]:checked').val();
-
 
1540
			if (mode === 'reschedule') {
-
 
1541
				var d = $row.find('.rl-date').val();
-
 
1542
				if (!d) {
-
 
1543
					problems.push('Lead ' + leadId + ': pick a date or switch to Cancel');
-
 
1544
					return;
-
 
1545
				}
-
 
1546
				actions.push({leadId: leadId, action: 'reschedule', toDate: d});
-
 
1547
			} else {
-
 
1548
				actions.push({leadId: leadId, action: 'cancel'});
-
 
1549
			}
-
 
1550
		});
-
 
1551
		if (problems.length) {
-
 
1552
			alert(problems.join('\n'));
-
 
1553
			return;
-
 
1554
		}
-
 
1555
		$('#modal-removed-leads').remove();
-
 
1556
		onConfirm(actions);
1269
});
1557
	});
-
 
1558
}
1270
 
1559
 
1271
// ============ PANEL TABS ============
1560
// ============ PANEL TABS ============
1272
$('.panel-tab').on('click', function () {
1561
$('.panel-tab').on('click', function () {
1273
	var tab = $(this).data('tab');
1562
	var tab = $(this).data('tab');
1274
	$('.panel-tab').removeClass('active');
1563
	$('.panel-tab').removeClass('active');