|
Lines 1072-1077
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1072 |
); |
1072 |
); |
| 1073 |
} |
1073 |
} |
| 1074 |
|
1074 |
|
|
|
1075 |
// Create feedback message container below the calendar |
| 1076 |
let feedbackDiv = periodPicker.calendarContainer.querySelector( |
| 1077 |
".booking-conflict-feedback" |
| 1078 |
); |
| 1079 |
if (!feedbackDiv) { |
| 1080 |
feedbackDiv = document.createElement("div"); |
| 1081 |
feedbackDiv.className = |
| 1082 |
"booking-conflict-feedback alert d-none"; |
| 1083 |
periodPicker.calendarContainer.appendChild(feedbackDiv); |
| 1084 |
} |
| 1085 |
|
| 1075 |
// Add hints for days before the start range and after the end range |
1086 |
// Add hints for days before the start range and after the end range |
| 1076 |
periodPicker.calendarContainer.addEventListener( |
1087 |
periodPicker.calendarContainer.addEventListener( |
| 1077 |
"mouseover", |
1088 |
"mouseover", |
|
Lines 1087-1101
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1087 |
) |
1098 |
) |
| 1088 |
: null; |
1099 |
: null; |
| 1089 |
|
1100 |
|
|
|
1101 |
// Calculate new booking's lead/trail periods |
| 1090 |
const leadStart = startDate |
1102 |
const leadStart = startDate |
| 1091 |
? startDate.subtract(leadDays, "day") |
1103 |
? startDate.subtract(leadDays, "day") |
| 1092 |
: hoverDate.subtract(leadDays, "day"); |
1104 |
: hoverDate.subtract(leadDays, "day"); |
| 1093 |
const leadEnd = startDate ? startDate : hoverDate; |
1105 |
const leadEnd = startDate |
| 1094 |
const trailStart = hoverDate; |
1106 |
? startDate.subtract(1, "day") |
| 1095 |
const trailEnd = hoverDate.add(trailDays, "day"); |
1107 |
: hoverDate.subtract(1, "day"); |
|
|
1108 |
const trailStart = startDate |
| 1109 |
? hoverDate.add(1, "day") |
| 1110 |
: hoverDate.add(1, "day"); |
| 1111 |
const trailEnd = startDate |
| 1112 |
? hoverDate.add(trailDays, "day") |
| 1113 |
: hoverDate.add(trailDays, "day"); |
| 1114 |
|
| 1115 |
// BIDIRECTIONAL ENHANCEMENT: Collect closest bookings for visual feedback |
| 1116 |
// and check for mathematical conflicts in a single pass |
| 1117 |
let closestBeforeBooking = null; |
| 1118 |
let closestBeforeDistance = Infinity; |
| 1119 |
|
| 1120 |
let closestAfterBooking = null; |
| 1121 |
let closestAfterDistance = Infinity; |
| 1096 |
|
1122 |
|
| 1097 |
let leadDisable = false; |
1123 |
let leadDisable = false; |
| 1098 |
let trailDisable = false; |
1124 |
let trailDisable = false; |
|
|
1125 |
|
| 1126 |
// Track conflict reasons for messaging |
| 1127 |
let leadConflictReason = { |
| 1128 |
withTrail: false, |
| 1129 |
withLead: false, |
| 1130 |
withBooking: false, |
| 1131 |
}; |
| 1132 |
let trailConflictReason = { |
| 1133 |
withTrail: false, |
| 1134 |
withLead: false, |
| 1135 |
withBooking: false, |
| 1136 |
}; |
| 1137 |
|
| 1138 |
bookings.forEach(booking => { |
| 1139 |
// Skip if we're editing this booking |
| 1140 |
if ( |
| 1141 |
booking_id && |
| 1142 |
booking_id == booking.booking_id |
| 1143 |
) { |
| 1144 |
return; |
| 1145 |
} |
| 1146 |
|
| 1147 |
// Skip if not same item (for item-specific bookings) |
| 1148 |
if ( |
| 1149 |
booking.item_id && |
| 1150 |
booking_item_id && |
| 1151 |
booking.item_id != booking_item_id |
| 1152 |
) { |
| 1153 |
return; |
| 1154 |
} |
| 1155 |
|
| 1156 |
const bookingStart = dayjs( |
| 1157 |
booking.start_date |
| 1158 |
).startOf("day"); |
| 1159 |
const bookingEnd = dayjs( |
| 1160 |
booking.end_date |
| 1161 |
).startOf("day"); |
| 1162 |
|
| 1163 |
// BIDIRECTIONAL: Mathematical checks for conflicts (works across month boundaries) |
| 1164 |
// Calculate this booking's full protected period |
| 1165 |
const existingLeadStart = bookingStart.subtract( |
| 1166 |
leadDays, |
| 1167 |
"day" |
| 1168 |
); |
| 1169 |
const existingLeadEnd = bookingStart.subtract( |
| 1170 |
1, |
| 1171 |
"day" |
| 1172 |
); |
| 1173 |
const existingTrailStart = bookingEnd.add( |
| 1174 |
1, |
| 1175 |
"day" |
| 1176 |
); |
| 1177 |
const existingTrailEnd = bookingEnd.add( |
| 1178 |
trailDays, |
| 1179 |
"day" |
| 1180 |
); |
| 1181 |
|
| 1182 |
// Check if new booking's LEAD period overlaps with existing booking |
| 1183 |
if (!periodPicker.selectedDates[0]) { |
| 1184 |
// Check overlap with existing booking's trail period |
| 1185 |
if ( |
| 1186 |
leadStart.isSameOrBefore( |
| 1187 |
existingTrailEnd |
| 1188 |
) && |
| 1189 |
leadEnd.isSameOrAfter( |
| 1190 |
existingTrailStart |
| 1191 |
) |
| 1192 |
) { |
| 1193 |
leadDisable = true; |
| 1194 |
leadConflictReason.withTrail = true; |
| 1195 |
} |
| 1196 |
// Check overlap with existing booking's lead period |
| 1197 |
else if ( |
| 1198 |
leadStart.isSameOrBefore( |
| 1199 |
existingLeadEnd |
| 1200 |
) && |
| 1201 |
leadEnd.isSameOrAfter(existingLeadStart) |
| 1202 |
) { |
| 1203 |
leadDisable = true; |
| 1204 |
leadConflictReason.withLead = true; |
| 1205 |
} |
| 1206 |
// Check overlap with existing booking itself |
| 1207 |
else if ( |
| 1208 |
leadStart.isSameOrBefore(bookingEnd) && |
| 1209 |
leadEnd.isSameOrAfter(bookingStart) |
| 1210 |
) { |
| 1211 |
leadDisable = true; |
| 1212 |
leadConflictReason.withBooking = true; |
| 1213 |
} |
| 1214 |
} |
| 1215 |
|
| 1216 |
// Check if new booking's TRAIL period overlaps with existing booking |
| 1217 |
if (periodPicker.selectedDates[0]) { |
| 1218 |
// Check overlap with existing booking's lead period |
| 1219 |
if ( |
| 1220 |
trailStart.isSameOrBefore( |
| 1221 |
existingLeadEnd |
| 1222 |
) && |
| 1223 |
trailEnd.isSameOrAfter( |
| 1224 |
existingLeadStart |
| 1225 |
) |
| 1226 |
) { |
| 1227 |
trailDisable = true; |
| 1228 |
trailConflictReason.withLead = true; |
| 1229 |
} |
| 1230 |
// Check overlap with existing booking's trail period |
| 1231 |
else if ( |
| 1232 |
trailStart.isSameOrBefore( |
| 1233 |
existingTrailEnd |
| 1234 |
) && |
| 1235 |
trailEnd.isSameOrAfter( |
| 1236 |
existingTrailStart |
| 1237 |
) |
| 1238 |
) { |
| 1239 |
trailDisable = true; |
| 1240 |
trailConflictReason.withTrail = true; |
| 1241 |
} |
| 1242 |
// Check overlap with existing booking itself |
| 1243 |
else if ( |
| 1244 |
trailStart.isSameOrBefore(bookingEnd) && |
| 1245 |
trailEnd.isSameOrAfter(bookingStart) |
| 1246 |
) { |
| 1247 |
trailDisable = true; |
| 1248 |
trailConflictReason.withBooking = true; |
| 1249 |
} |
| 1250 |
} |
| 1251 |
|
| 1252 |
// Find closest bookings for visual feedback (when dates are in view) |
| 1253 |
if (bookingEnd.isBefore(hoverDate)) { |
| 1254 |
const distance = hoverDate.diff( |
| 1255 |
bookingEnd, |
| 1256 |
"day" |
| 1257 |
); |
| 1258 |
if (distance < closestBeforeDistance) { |
| 1259 |
closestBeforeDistance = distance; |
| 1260 |
closestBeforeBooking = { |
| 1261 |
start: bookingStart, |
| 1262 |
end: bookingEnd, |
| 1263 |
}; |
| 1264 |
} |
| 1265 |
} |
| 1266 |
|
| 1267 |
if (bookingStart.isAfter(hoverDate)) { |
| 1268 |
const distance = bookingStart.diff( |
| 1269 |
hoverDate, |
| 1270 |
"day" |
| 1271 |
); |
| 1272 |
if (distance < closestAfterDistance) { |
| 1273 |
closestAfterDistance = distance; |
| 1274 |
closestAfterBooking = { |
| 1275 |
start: bookingStart, |
| 1276 |
end: bookingEnd, |
| 1277 |
}; |
| 1278 |
} |
| 1279 |
} |
| 1280 |
}); |
| 1281 |
|
| 1282 |
// Work through all days in view and add classes appropraitely based on hovered date |
| 1099 |
periodPicker.calendarContainer |
1283 |
periodPicker.calendarContainer |
| 1100 |
.querySelectorAll(".flatpickr-day") |
1284 |
.querySelectorAll(".flatpickr-day") |
| 1101 |
.forEach(function (dayElem) { |
1285 |
.forEach(function (dayElem) { |
|
Lines 1103-1135
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1103 |
dayElem.dateObj |
1287 |
dayElem.dateObj |
| 1104 |
).startOf("day"); |
1288 |
).startOf("day"); |
| 1105 |
|
1289 |
|
| 1106 |
dayElem.classList.toggle( |
1290 |
// Clear existing booking lead/trail classes (including start/end) |
| 1107 |
"leadRangeStart", |
1291 |
dayElem.classList.remove( |
| 1108 |
elemDate.isSame(leadStart) |
1292 |
"existingBookingLead" |
| 1109 |
); |
1293 |
); |
| 1110 |
dayElem.classList.toggle( |
1294 |
dayElem.classList.remove( |
| 1111 |
"leadRange", |
1295 |
"existingBookingLeadStart" |
| 1112 |
elemDate.isSameOrAfter(leadStart) && |
|
|
| 1113 |
elemDate.isBefore(leadEnd) |
| 1114 |
); |
1296 |
); |
| 1115 |
dayElem.classList.toggle( |
1297 |
dayElem.classList.remove( |
| 1116 |
"leadRangeEnd", |
1298 |
"existingBookingLeadEnd" |
| 1117 |
elemDate.isSame(leadEnd) |
|
|
| 1118 |
); |
1299 |
); |
| 1119 |
dayElem.classList.toggle( |
1300 |
dayElem.classList.remove( |
| 1120 |
"trailRangeStart", |
1301 |
"existingBookingTrail" |
| 1121 |
elemDate.isSame(trailStart) |
|
|
| 1122 |
); |
1302 |
); |
| 1123 |
dayElem.classList.toggle( |
1303 |
dayElem.classList.remove( |
| 1124 |
"trailRange", |
1304 |
"existingBookingTrailStart" |
| 1125 |
elemDate.isAfter(trailStart) && |
|
|
| 1126 |
elemDate.isSameOrBefore(trailEnd) |
| 1127 |
); |
1305 |
); |
| 1128 |
dayElem.classList.toggle( |
1306 |
dayElem.classList.remove( |
| 1129 |
"trailRangeEnd", |
1307 |
"existingBookingTrailEnd" |
| 1130 |
elemDate.isSame(trailEnd) |
|
|
| 1131 |
); |
1308 |
); |
| 1132 |
// If we're overlapping a disabled date, disable our hoverDate |
1309 |
|
|
|
1310 |
// Apply proposed booking's lead/trail period classes |
| 1311 |
// Only apply lead classes if lead period > 0 |
| 1312 |
if (leadDays > 0) { |
| 1313 |
dayElem.classList.toggle( |
| 1314 |
"leadRangeStart", |
| 1315 |
elemDate.isSame(leadStart) |
| 1316 |
); |
| 1317 |
dayElem.classList.toggle( |
| 1318 |
"leadRange", |
| 1319 |
elemDate.isSameOrAfter(leadStart) && |
| 1320 |
elemDate.isSameOrBefore(leadEnd) |
| 1321 |
); |
| 1322 |
dayElem.classList.toggle( |
| 1323 |
"leadRangeEnd", |
| 1324 |
elemDate.isSame(leadEnd) |
| 1325 |
); |
| 1326 |
} |
| 1327 |
|
| 1328 |
// Only apply trail classes if trail period > 0 |
| 1329 |
if (trailDays > 0) { |
| 1330 |
dayElem.classList.toggle( |
| 1331 |
"trailRangeStart", |
| 1332 |
elemDate.isSame(trailStart) |
| 1333 |
); |
| 1334 |
dayElem.classList.toggle( |
| 1335 |
"trailRange", |
| 1336 |
elemDate.isSameOrAfter( |
| 1337 |
trailStart |
| 1338 |
) && |
| 1339 |
elemDate.isSameOrBefore( |
| 1340 |
trailEnd |
| 1341 |
) |
| 1342 |
); |
| 1343 |
dayElem.classList.toggle( |
| 1344 |
"trailRangeEnd", |
| 1345 |
elemDate.isSame(trailEnd) |
| 1346 |
); |
| 1347 |
} |
| 1348 |
|
| 1349 |
// Show closest preceding existing booking's trail period |
| 1350 |
if (closestBeforeBooking && trailDays > 0) { |
| 1351 |
const existingTrailStart = |
| 1352 |
closestBeforeBooking.end.add( |
| 1353 |
1, |
| 1354 |
"day" |
| 1355 |
); |
| 1356 |
const existingTrailEnd = |
| 1357 |
closestBeforeBooking.end.add( |
| 1358 |
trailDays, |
| 1359 |
"day" |
| 1360 |
); |
| 1361 |
|
| 1362 |
if ( |
| 1363 |
elemDate.isSameOrAfter( |
| 1364 |
existingTrailStart |
| 1365 |
) && |
| 1366 |
elemDate.isSameOrBefore( |
| 1367 |
existingTrailEnd |
| 1368 |
) |
| 1369 |
) { |
| 1370 |
dayElem.classList.add( |
| 1371 |
"existingBookingTrail" |
| 1372 |
); |
| 1373 |
// Add start/end classes for rounded borders |
| 1374 |
if ( |
| 1375 |
elemDate.isSame( |
| 1376 |
existingTrailStart |
| 1377 |
) |
| 1378 |
) { |
| 1379 |
dayElem.classList.add( |
| 1380 |
"existingBookingTrailStart" |
| 1381 |
); |
| 1382 |
} |
| 1383 |
if ( |
| 1384 |
elemDate.isSame( |
| 1385 |
existingTrailEnd |
| 1386 |
) |
| 1387 |
) { |
| 1388 |
dayElem.classList.add( |
| 1389 |
"existingBookingTrailEnd" |
| 1390 |
); |
| 1391 |
} |
| 1392 |
} |
| 1393 |
} |
| 1394 |
|
| 1395 |
// Show closest following existing booking's lead period |
| 1396 |
if (closestAfterBooking && leadDays > 0) { |
| 1397 |
const existingLeadStart = |
| 1398 |
closestAfterBooking.start.subtract( |
| 1399 |
leadDays, |
| 1400 |
"day" |
| 1401 |
); |
| 1402 |
const existingLeadEnd = |
| 1403 |
closestAfterBooking.start.subtract( |
| 1404 |
1, |
| 1405 |
"day" |
| 1406 |
); |
| 1407 |
|
| 1408 |
if ( |
| 1409 |
elemDate.isSameOrAfter( |
| 1410 |
existingLeadStart |
| 1411 |
) && |
| 1412 |
elemDate.isSameOrBefore( |
| 1413 |
existingLeadEnd |
| 1414 |
) |
| 1415 |
) { |
| 1416 |
dayElem.classList.add( |
| 1417 |
"existingBookingLead" |
| 1418 |
); |
| 1419 |
// Add start/end classes for rounded borders |
| 1420 |
if ( |
| 1421 |
elemDate.isSame( |
| 1422 |
existingLeadStart |
| 1423 |
) |
| 1424 |
) { |
| 1425 |
dayElem.classList.add( |
| 1426 |
"existingBookingLeadStart" |
| 1427 |
); |
| 1428 |
} |
| 1429 |
if ( |
| 1430 |
elemDate.isSame(existingLeadEnd) |
| 1431 |
) { |
| 1432 |
dayElem.classList.add( |
| 1433 |
"existingBookingLeadEnd" |
| 1434 |
); |
| 1435 |
} |
| 1436 |
} |
| 1437 |
} |
| 1438 |
|
| 1439 |
// Check for conflicts with flatpickr-disabled dates |
| 1133 |
if ( |
1440 |
if ( |
| 1134 |
dayElem.classList.contains( |
1441 |
dayElem.classList.contains( |
| 1135 |
"flatpickr-disabled" |
1442 |
"flatpickr-disabled" |
|
Lines 1138-1149
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1138 |
if ( |
1445 |
if ( |
| 1139 |
!periodPicker.selectedDates[0] && |
1446 |
!periodPicker.selectedDates[0] && |
| 1140 |
elemDate.isSameOrAfter(leadStart) && |
1447 |
elemDate.isSameOrAfter(leadStart) && |
| 1141 |
elemDate.isBefore(leadEnd) |
1448 |
elemDate.isSameOrBefore(leadEnd) |
| 1142 |
) { |
1449 |
) { |
| 1143 |
leadDisable = true; |
1450 |
leadDisable = true; |
| 1144 |
} |
1451 |
} |
| 1145 |
if ( |
1452 |
if ( |
| 1146 |
elemDate.isAfter(trailStart) && |
1453 |
periodPicker.selectedDates[0] && |
|
|
1454 |
elemDate.isSameOrAfter( |
| 1455 |
trailStart |
| 1456 |
) && |
| 1147 |
elemDate.isSameOrBefore(trailEnd) |
1457 |
elemDate.isSameOrBefore(trailEnd) |
| 1148 |
) { |
1458 |
) { |
| 1149 |
// Only consider this a conflict if the disabled date is within the max date range |
1459 |
// Only consider this a conflict if the disabled date is within the max date range |
|
Lines 1163-1168
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1163 |
} |
1473 |
} |
| 1164 |
} |
1474 |
} |
| 1165 |
} |
1475 |
} |
|
|
1476 |
|
| 1477 |
// Check for conflicts with existing booking's trail period |
| 1478 |
if ( |
| 1479 |
!periodPicker.selectedDates[0] && |
| 1480 |
dayElem.classList.contains( |
| 1481 |
"existingBookingTrail" |
| 1482 |
) |
| 1483 |
) { |
| 1484 |
// New booking's lead period overlaps with existing booking's trail |
| 1485 |
if ( |
| 1486 |
elemDate.isSameOrAfter(leadStart) && |
| 1487 |
elemDate.isSameOrBefore(leadEnd) |
| 1488 |
) { |
| 1489 |
leadDisable = true; |
| 1490 |
} |
| 1491 |
} |
| 1492 |
|
| 1493 |
// Check for conflicts with existing booking's lead period |
| 1494 |
if ( |
| 1495 |
periodPicker.selectedDates[0] && |
| 1496 |
dayElem.classList.contains( |
| 1497 |
"existingBookingLead" |
| 1498 |
) |
| 1499 |
) { |
| 1500 |
// New booking's trail period overlaps with existing booking's lead |
| 1501 |
if ( |
| 1502 |
elemDate.isSameOrAfter( |
| 1503 |
trailStart |
| 1504 |
) && |
| 1505 |
elemDate.isSameOrBefore(trailEnd) |
| 1506 |
) { |
| 1507 |
trailDisable = true; |
| 1508 |
} |
| 1509 |
} |
| 1510 |
|
| 1166 |
dayElem.classList.remove("leadDisable"); |
1511 |
dayElem.classList.remove("leadDisable"); |
| 1167 |
dayElem.classList.remove("trailDisable"); |
1512 |
dayElem.classList.remove("trailDisable"); |
| 1168 |
dayElem.removeEventListener( |
1513 |
dayElem.removeEventListener( |
|
Lines 1172-1177
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1172 |
); |
1517 |
); |
| 1173 |
}); |
1518 |
}); |
| 1174 |
|
1519 |
|
|
|
1520 |
// Additional check for hovering directly on existing booking's lead/trail periods |
| 1521 |
// If hovering on an existing booking's lead period when selecting start date, block selection |
| 1522 |
if ( |
| 1523 |
!periodPicker.selectedDates[0] && |
| 1524 |
target.classList.contains("existingBookingLead") |
| 1525 |
) { |
| 1526 |
leadDisable = true; |
| 1527 |
} |
| 1528 |
|
| 1529 |
// If hovering on an existing booking's trail period when selecting end date, block selection |
| 1530 |
if ( |
| 1531 |
periodPicker.selectedDates[0] && |
| 1532 |
target.classList.contains( |
| 1533 |
"existingBookingTrail" |
| 1534 |
) |
| 1535 |
) { |
| 1536 |
trailDisable = true; |
| 1537 |
} |
| 1538 |
|
| 1175 |
if (leadDisable) { |
1539 |
if (leadDisable) { |
| 1176 |
target.classList.add("leadDisable"); |
1540 |
target.classList.add("leadDisable"); |
| 1177 |
} |
1541 |
} |
|
Lines 1185-1190
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1185 |
true |
1549 |
true |
| 1186 |
); |
1550 |
); |
| 1187 |
} |
1551 |
} |
|
|
1552 |
|
| 1553 |
// Update feedback message |
| 1554 |
const feedbackDiv = |
| 1555 |
periodPicker.calendarContainer.querySelector( |
| 1556 |
".booking-conflict-feedback" |
| 1557 |
); |
| 1558 |
if (feedbackDiv) { |
| 1559 |
let message = ""; |
| 1560 |
let messageType = "info"; // info, warning, error |
| 1561 |
|
| 1562 |
// Determine what the hovered date is (needed for both error and info messages) |
| 1563 |
const today = dayjs().startOf("day"); |
| 1564 |
const isDisabled = |
| 1565 |
target.classList.contains( |
| 1566 |
"flatpickr-disabled" |
| 1567 |
); |
| 1568 |
const isInExistingLead = |
| 1569 |
target.classList.contains( |
| 1570 |
"existingBookingLead" |
| 1571 |
); |
| 1572 |
const isInExistingTrail = |
| 1573 |
target.classList.contains( |
| 1574 |
"existingBookingTrail" |
| 1575 |
); |
| 1576 |
|
| 1577 |
// Generate appropriate feedback messages based on conflicts |
| 1578 |
if (leadDisable || trailDisable) { |
| 1579 |
messageType = "error"; |
| 1580 |
|
| 1581 |
// When selecting START date (no date selected yet) |
| 1582 |
if (!periodPicker.selectedDates[0]) { |
| 1583 |
// Check direct state first (what IS this date?) |
| 1584 |
if (hoverDate.isBefore(today)) { |
| 1585 |
message = __( |
| 1586 |
"Cannot select: date is in the past" |
| 1587 |
); |
| 1588 |
} else if (isDisabled) { |
| 1589 |
message = __( |
| 1590 |
"Cannot select: this date is part of an existing booking" |
| 1591 |
); |
| 1592 |
} else if (isInExistingLead) { |
| 1593 |
message = __( |
| 1594 |
"Cannot select: this date is part of an existing booking's lead period" |
| 1595 |
); |
| 1596 |
} else if (isInExistingTrail) { |
| 1597 |
message = __( |
| 1598 |
"Cannot select: this date is part of an existing booking's trail period" |
| 1599 |
); |
| 1600 |
} |
| 1601 |
// Then check calculated lead period conflicts |
| 1602 |
else if ( |
| 1603 |
leadDays > 0 && |
| 1604 |
leadStart.isSameOrBefore(today) |
| 1605 |
) { |
| 1606 |
message = |
| 1607 |
__("Cannot select") + |
| 1608 |
": " + |
| 1609 |
__( |
| 1610 |
"insufficient lead time (%s days required before start)" |
| 1611 |
).format(leadDays); |
| 1612 |
} else if (leadDays > 0) { |
| 1613 |
// Use mathematical conflict detection (works across month boundaries) |
| 1614 |
if (leadConflictReason.withTrail) { |
| 1615 |
message = |
| 1616 |
__("Cannot select") + |
| 1617 |
": " + |
| 1618 |
__( |
| 1619 |
"lead period (%s days before start) conflicts with an existing booking's trail period" |
| 1620 |
).format(leadDays); |
| 1621 |
} else if ( |
| 1622 |
leadConflictReason.withLead |
| 1623 |
) { |
| 1624 |
message = |
| 1625 |
__("Cannot select") + |
| 1626 |
": " + |
| 1627 |
__( |
| 1628 |
"lead period (%s days before start) conflicts with an existing booking's lead period" |
| 1629 |
).format(leadDays); |
| 1630 |
} else if ( |
| 1631 |
leadConflictReason.withBooking |
| 1632 |
) { |
| 1633 |
message = |
| 1634 |
__("Cannot select") + |
| 1635 |
": " + |
| 1636 |
__( |
| 1637 |
"lead period (%s days before start) conflicts with an existing booking" |
| 1638 |
).format(leadDays); |
| 1639 |
} |
| 1640 |
} else { |
| 1641 |
message = __( |
| 1642 |
"Cannot select: conflicts with an existing booking" |
| 1643 |
); |
| 1644 |
} |
| 1645 |
} |
| 1646 |
// When selecting END date (start date already selected) |
| 1647 |
else if (periodPicker.selectedDates[0]) { |
| 1648 |
// Check direct state first (what IS this date?) |
| 1649 |
if (isDisabled) { |
| 1650 |
message = __( |
| 1651 |
"Cannot select: this date is part of an existing booking" |
| 1652 |
); |
| 1653 |
} else if (isInExistingLead) { |
| 1654 |
message = __( |
| 1655 |
"Cannot select: this date is part of an existing booking's lead period" |
| 1656 |
); |
| 1657 |
} else if (isInExistingTrail) { |
| 1658 |
message = __( |
| 1659 |
"Cannot select: this date is part of an existing booking's trail period" |
| 1660 |
); |
| 1661 |
} |
| 1662 |
// Then check calculated trail period conflicts |
| 1663 |
else if (trailDays > 0) { |
| 1664 |
// Use mathematical conflict detection (works across month boundaries) |
| 1665 |
if (trailConflictReason.withLead) { |
| 1666 |
message = |
| 1667 |
__("Cannot select") + |
| 1668 |
": " + |
| 1669 |
__( |
| 1670 |
"trail period (%s days after return) conflicts with an existing booking's lead period" |
| 1671 |
).format(trailDays); |
| 1672 |
} else if ( |
| 1673 |
trailConflictReason.withTrail |
| 1674 |
) { |
| 1675 |
message = |
| 1676 |
__("Cannot select") + |
| 1677 |
": " + |
| 1678 |
__( |
| 1679 |
"trail period (%s days after return) conflicts with an existing booking's trail period" |
| 1680 |
).format(trailDays); |
| 1681 |
} else if ( |
| 1682 |
trailConflictReason.withBooking |
| 1683 |
) { |
| 1684 |
message = |
| 1685 |
__("Cannot select") + |
| 1686 |
": " + |
| 1687 |
__( |
| 1688 |
"trail period (%s days after return) conflicts with an existing booking" |
| 1689 |
).format(trailDays); |
| 1690 |
} |
| 1691 |
} else { |
| 1692 |
message = __( |
| 1693 |
"Cannot select: conflicts with existing an booking" |
| 1694 |
); |
| 1695 |
} |
| 1696 |
} |
| 1697 |
} else { |
| 1698 |
// Show helpful info messages when no conflicts |
| 1699 |
if (!periodPicker.selectedDates[0]) { |
| 1700 |
// When selecting start date, show both lead and trail info |
| 1701 |
if (leadDays > 0 && trailDays > 0) { |
| 1702 |
message = |
| 1703 |
__("Selecting start date") + |
| 1704 |
". " + |
| 1705 |
__( |
| 1706 |
"Lead period: %s days before start" |
| 1707 |
).format(leadDays) + |
| 1708 |
". " + |
| 1709 |
__( |
| 1710 |
"Trail period: %s days after return" |
| 1711 |
).format(trailDays); |
| 1712 |
} else if (leadDays > 0) { |
| 1713 |
message = |
| 1714 |
__("Selecting start date") + |
| 1715 |
". " + |
| 1716 |
__( |
| 1717 |
"Lead period: %s days before start" |
| 1718 |
).format(leadDays); |
| 1719 |
} else if (trailDays > 0) { |
| 1720 |
message = |
| 1721 |
__("Selecting start date") + |
| 1722 |
". " + |
| 1723 |
__( |
| 1724 |
"Trail period: %s days after return" |
| 1725 |
).format(trailDays); |
| 1726 |
} else { |
| 1727 |
message = __( |
| 1728 |
"Selecting start date" |
| 1729 |
); |
| 1730 |
} |
| 1731 |
messageType = "info"; |
| 1732 |
} else { |
| 1733 |
if (trailDays > 0) { |
| 1734 |
message = |
| 1735 |
__("Selecting end date") + |
| 1736 |
". " + |
| 1737 |
__( |
| 1738 |
"Trail period: %s days after return" |
| 1739 |
).format(trailDays); |
| 1740 |
} else { |
| 1741 |
message = __("Selecting end date"); |
| 1742 |
} |
| 1743 |
messageType = "info"; |
| 1744 |
} |
| 1745 |
|
| 1746 |
// Show additional context if hovering over existing booking periods |
| 1747 |
if (isInExistingLead) { |
| 1748 |
message += |
| 1749 |
" • " + |
| 1750 |
__( |
| 1751 |
"hovering existing booking's lead period" |
| 1752 |
); |
| 1753 |
} else if (isInExistingTrail) { |
| 1754 |
message += |
| 1755 |
" • " + |
| 1756 |
__( |
| 1757 |
"hovering existing booking's trail period" |
| 1758 |
); |
| 1759 |
} |
| 1760 |
} |
| 1761 |
|
| 1762 |
feedbackDiv.textContent = message; |
| 1763 |
feedbackDiv.classList.remove( |
| 1764 |
"alert-danger", |
| 1765 |
"alert-warning", |
| 1766 |
"alert-info" |
| 1767 |
); |
| 1768 |
|
| 1769 |
if (message) { |
| 1770 |
feedbackDiv.classList.remove("d-none"); |
| 1771 |
// Apply appropriate Bootstrap alert class based on message type |
| 1772 |
if (messageType === "error") { |
| 1773 |
feedbackDiv.classList.add( |
| 1774 |
"alert-danger" |
| 1775 |
); |
| 1776 |
} else if (messageType === "warning") { |
| 1777 |
feedbackDiv.classList.add( |
| 1778 |
"alert-warning" |
| 1779 |
); |
| 1780 |
} else { |
| 1781 |
feedbackDiv.classList.add("alert-info"); |
| 1782 |
} |
| 1783 |
} else { |
| 1784 |
feedbackDiv.classList.add("d-none"); |
| 1785 |
} |
| 1786 |
} |
| 1188 |
} |
1787 |
} |
| 1189 |
} |
1788 |
} |
| 1190 |
); |
1789 |
); |
| 1191 |
- |
|
|