|
Lines 937-942
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 937 |
) |
937 |
) |
| 938 |
: null; |
938 |
: null; |
| 939 |
|
939 |
|
|
|
940 |
// Calculate new booking's lead/trail periods |
| 940 |
const leadStart = startDate |
941 |
const leadStart = startDate |
| 941 |
? startDate.subtract(leadDays, "day") |
942 |
? startDate.subtract(leadDays, "day") |
| 942 |
: hoverDate.subtract(leadDays, "day"); |
943 |
: hoverDate.subtract(leadDays, "day"); |
|
Lines 944-951
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 944 |
const trailStart = hoverDate; |
945 |
const trailStart = hoverDate; |
| 945 |
const trailEnd = hoverDate.add(trailDays, "day"); |
946 |
const trailEnd = hoverDate.add(trailDays, "day"); |
| 946 |
|
947 |
|
|
|
948 |
// BIDIRECTIONAL ENHANCEMENT: Collect closest bookings for visual feedback |
| 949 |
// and check for mathematical conflicts in a single pass |
| 950 |
let closestBeforeBooking = null; |
| 951 |
let closestBeforeDistance = Infinity; |
| 952 |
|
| 953 |
let closestAfterBooking = null; |
| 954 |
let closestAfterDistance = Infinity; |
| 955 |
|
| 947 |
let leadDisable = false; |
956 |
let leadDisable = false; |
| 948 |
let trailDisable = false; |
957 |
let trailDisable = false; |
|
|
958 |
|
| 959 |
bookings.forEach(booking => { |
| 960 |
// Skip if we're editing this booking |
| 961 |
if ( |
| 962 |
booking_id && |
| 963 |
booking_id == booking.booking_id |
| 964 |
) { |
| 965 |
return; |
| 966 |
} |
| 967 |
|
| 968 |
// Skip if not same item (for item-specific bookings) |
| 969 |
if ( |
| 970 |
booking.item_id && |
| 971 |
booking_item_id && |
| 972 |
booking.item_id != booking_item_id |
| 973 |
) { |
| 974 |
return; |
| 975 |
} |
| 976 |
|
| 977 |
const bookingStart = dayjs( |
| 978 |
booking.start_date |
| 979 |
).startOf("day"); |
| 980 |
const bookingEnd = dayjs( |
| 981 |
booking.end_date |
| 982 |
).startOf("day"); |
| 983 |
|
| 984 |
// BIDIRECTIONAL: Mathematical checks for conflicts (works across month boundaries) |
| 985 |
// Calculate this booking's full protected period |
| 986 |
const existingLeadStart = bookingStart.subtract( |
| 987 |
leadDays, |
| 988 |
"day" |
| 989 |
); |
| 990 |
const existingLeadEnd = bookingStart.subtract( |
| 991 |
1, |
| 992 |
"day" |
| 993 |
); |
| 994 |
const existingTrailStart = bookingEnd.add( |
| 995 |
1, |
| 996 |
"day" |
| 997 |
); |
| 998 |
const existingTrailEnd = bookingEnd.add( |
| 999 |
trailDays, |
| 1000 |
"day" |
| 1001 |
); |
| 1002 |
|
| 1003 |
// Check if new booking's LEAD period overlaps with existing booking's TRAIL period |
| 1004 |
if (!periodPicker.selectedDates[0]) { |
| 1005 |
if ( |
| 1006 |
leadStart.isSameOrBefore( |
| 1007 |
existingTrailEnd |
| 1008 |
) && |
| 1009 |
leadEnd.isSameOrAfter( |
| 1010 |
existingTrailStart |
| 1011 |
) |
| 1012 |
) { |
| 1013 |
leadDisable = true; |
| 1014 |
} |
| 1015 |
} |
| 1016 |
|
| 1017 |
// Check if new booking's TRAIL period overlaps with existing booking's LEAD period |
| 1018 |
if (periodPicker.selectedDates[0]) { |
| 1019 |
if ( |
| 1020 |
trailStart.isSameOrBefore( |
| 1021 |
existingLeadEnd |
| 1022 |
) && |
| 1023 |
trailEnd.isSameOrAfter( |
| 1024 |
existingLeadStart |
| 1025 |
) |
| 1026 |
) { |
| 1027 |
trailDisable = true; |
| 1028 |
} |
| 1029 |
} |
| 1030 |
|
| 1031 |
// Find closest bookings for visual feedback (when dates are in view) |
| 1032 |
if (bookingEnd.isBefore(hoverDate)) { |
| 1033 |
const distance = hoverDate.diff( |
| 1034 |
bookingEnd, |
| 1035 |
"day" |
| 1036 |
); |
| 1037 |
if (distance < closestBeforeDistance) { |
| 1038 |
closestBeforeDistance = distance; |
| 1039 |
closestBeforeBooking = { |
| 1040 |
start: bookingStart, |
| 1041 |
end: bookingEnd, |
| 1042 |
}; |
| 1043 |
} |
| 1044 |
} |
| 1045 |
|
| 1046 |
if (bookingStart.isAfter(hoverDate)) { |
| 1047 |
const distance = bookingStart.diff( |
| 1048 |
hoverDate, |
| 1049 |
"day" |
| 1050 |
); |
| 1051 |
if (distance < closestAfterDistance) { |
| 1052 |
closestAfterDistance = distance; |
| 1053 |
closestAfterBooking = { |
| 1054 |
start: bookingStart, |
| 1055 |
end: bookingEnd, |
| 1056 |
}; |
| 1057 |
} |
| 1058 |
} |
| 1059 |
}); |
| 1060 |
|
| 949 |
periodPicker.calendarContainer |
1061 |
periodPicker.calendarContainer |
| 950 |
.querySelectorAll(".flatpickr-day") |
1062 |
.querySelectorAll(".flatpickr-day") |
| 951 |
.forEach(function (dayElem) { |
1063 |
.forEach(function (dayElem) { |
|
Lines 953-958
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 953 |
dayElem.dateObj |
1065 |
dayElem.dateObj |
| 954 |
).startOf("day"); |
1066 |
).startOf("day"); |
| 955 |
|
1067 |
|
|
|
1068 |
// Clear existing booking lead/trail classes |
| 1069 |
dayElem.classList.remove( |
| 1070 |
"existingBookingLead" |
| 1071 |
); |
| 1072 |
dayElem.classList.remove( |
| 1073 |
"existingBookingTrail" |
| 1074 |
); |
| 1075 |
|
| 1076 |
// Apply new booking's lead/trail period classes |
| 956 |
dayElem.classList.toggle( |
1077 |
dayElem.classList.toggle( |
| 957 |
"leadRangeStart", |
1078 |
"leadRangeStart", |
| 958 |
elemDate.isSame(leadStart) |
1079 |
elemDate.isSame(leadStart) |
|
Lines 979-985
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 979 |
"trailRangeEnd", |
1100 |
"trailRangeEnd", |
| 980 |
elemDate.isSame(trailEnd) |
1101 |
elemDate.isSame(trailEnd) |
| 981 |
); |
1102 |
); |
| 982 |
// If we're overlapping a disabled date, disable our hoverDate |
1103 |
|
|
|
1104 |
// BIDIRECTIONAL: Show closest existing booking's trail period |
| 1105 |
if (closestBeforeBooking && trailDays > 0) { |
| 1106 |
const existingTrailStart = |
| 1107 |
closestBeforeBooking.end.add( |
| 1108 |
1, |
| 1109 |
"day" |
| 1110 |
); |
| 1111 |
const existingTrailEnd = |
| 1112 |
closestBeforeBooking.end.add( |
| 1113 |
trailDays, |
| 1114 |
"day" |
| 1115 |
); |
| 1116 |
|
| 1117 |
if ( |
| 1118 |
elemDate.isSameOrAfter( |
| 1119 |
existingTrailStart |
| 1120 |
) && |
| 1121 |
elemDate.isSameOrBefore( |
| 1122 |
existingTrailEnd |
| 1123 |
) |
| 1124 |
) { |
| 1125 |
dayElem.classList.add( |
| 1126 |
"existingBookingTrail" |
| 1127 |
); |
| 1128 |
} |
| 1129 |
} |
| 1130 |
|
| 1131 |
// BIDIRECTIONAL: Show closest existing booking's lead period |
| 1132 |
if (closestAfterBooking && leadDays > 0) { |
| 1133 |
const existingLeadStart = |
| 1134 |
closestAfterBooking.start.subtract( |
| 1135 |
leadDays, |
| 1136 |
"day" |
| 1137 |
); |
| 1138 |
const existingLeadEnd = |
| 1139 |
closestAfterBooking.start.subtract( |
| 1140 |
1, |
| 1141 |
"day" |
| 1142 |
); |
| 1143 |
|
| 1144 |
if ( |
| 1145 |
elemDate.isSameOrAfter( |
| 1146 |
existingLeadStart |
| 1147 |
) && |
| 1148 |
elemDate.isSameOrBefore( |
| 1149 |
existingLeadEnd |
| 1150 |
) |
| 1151 |
) { |
| 1152 |
dayElem.classList.add( |
| 1153 |
"existingBookingLead" |
| 1154 |
); |
| 1155 |
} |
| 1156 |
} |
| 1157 |
|
| 1158 |
// Check for conflicts with flatpickr-disabled dates (original behavior) |
| 983 |
if ( |
1159 |
if ( |
| 984 |
dayElem.classList.contains( |
1160 |
dayElem.classList.contains( |
| 985 |
"flatpickr-disabled" |
1161 |
"flatpickr-disabled" |
|
Lines 1013-1018
$("#placeBookingModal").on("show.bs.modal", function (e) {
Link Here
|
| 1013 |
} |
1189 |
} |
| 1014 |
} |
1190 |
} |
| 1015 |
} |
1191 |
} |
|
|
1192 |
|
| 1193 |
// BIDIRECTIONAL: Check for conflicts with existing booking's trail period |
| 1194 |
if ( |
| 1195 |
!periodPicker.selectedDates[0] && |
| 1196 |
dayElem.classList.contains( |
| 1197 |
"existingBookingTrail" |
| 1198 |
) |
| 1199 |
) { |
| 1200 |
// New booking's lead period overlaps with existing booking's trail |
| 1201 |
if ( |
| 1202 |
elemDate.isSameOrAfter(leadStart) && |
| 1203 |
elemDate.isBefore(leadEnd) |
| 1204 |
) { |
| 1205 |
leadDisable = true; |
| 1206 |
} |
| 1207 |
} |
| 1208 |
|
| 1209 |
// BIDIRECTIONAL: Check for conflicts with existing booking's lead period |
| 1210 |
if ( |
| 1211 |
dayElem.classList.contains( |
| 1212 |
"existingBookingLead" |
| 1213 |
) |
| 1214 |
) { |
| 1215 |
// New booking's trail period overlaps with existing booking's lead |
| 1216 |
if ( |
| 1217 |
elemDate.isAfter(trailStart) && |
| 1218 |
elemDate.isSameOrBefore(trailEnd) |
| 1219 |
) { |
| 1220 |
trailDisable = true; |
| 1221 |
} |
| 1222 |
} |
| 1223 |
|
| 1016 |
dayElem.classList.remove("leadDisable"); |
1224 |
dayElem.classList.remove("leadDisable"); |
| 1017 |
dayElem.classList.remove("trailDisable"); |
1225 |
dayElem.classList.remove("trailDisable"); |
| 1018 |
dayElem.removeEventListener( |
1226 |
dayElem.removeEventListener( |
| 1019 |
- |
|
|