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