/**
* Recommended Fit functionality for adding new visits with recommendations.
*
* This module provides:
* - Modal form for adding new visits
* - Integration with the recommendation API
* - Application of selected recommendations
*/
// Customer type configurations (must match CUSTOMER_TYPES in app.js and demo_data.py)
const VISIT_CUSTOMER_TYPES = {
RESIDENTIAL: { label: "Residential", icon: "fa-home", color: "#10b981", windowStart: "17:00", windowEnd: "20:00", minDemand: 1, maxDemand: 2, minService: 5, maxService: 10 },
BUSINESS: { label: "Business", icon: "fa-building", color: "#3b82f6", windowStart: "09:00", windowEnd: "17:00", minDemand: 3, maxDemand: 6, minService: 15, maxService: 30 },
RESTAURANT: { label: "Restaurant", icon: "fa-utensils", color: "#f59e0b", windowStart: "06:00", windowEnd: "10:00", minDemand: 5, maxDemand: 10, minService: 20, maxService: 40 },
};
function addNewVisit(id, lat, lng, map, marker) {
$('#newVisitModal').modal('show');
const visitModalContent = $("#newVisitModalContent");
visitModalContent.children().remove();
let visitForm = "";
// Customer Type Selection (prominent at the top)
visitForm += "
";
// Name and Location row
visitForm += "";
// Cargo and Duration row
visitForm += "";
// Time window row
visitForm += "";
visitModalContent.append(visitForm);
// Initialize with Residential defaults
const defaultType = VISIT_CUSTOMER_TYPES.RESIDENTIAL;
const tomorrow = JSJoda.LocalDate.now().plusDays(1);
function parseTimeToDateTime(timeStr) {
const [hours, minutes] = timeStr.split(':').map(Number);
return tomorrow.atTime(JSJoda.LocalTime.of(hours, minutes));
}
let minStartPicker = flatpickr("#inputMinStartTime", {
enableTime: true,
dateFormat: "Y-m-d H:i",
defaultDate: parseTimeToDateTime(defaultType.windowStart).format(JSJoda.DateTimeFormatter.ofPattern('yyyy-M-d HH:mm'))
});
let maxEndPicker = flatpickr("#inputMaxStartTime", {
enableTime: true,
dateFormat: "Y-m-d H:i",
defaultDate: parseTimeToDateTime(defaultType.windowEnd).format(JSJoda.DateTimeFormatter.ofPattern('yyyy-M-d HH:mm'))
});
// Customer type button click handler
$(".customer-type-btn").click(function() {
const selectedType = $(this).data('type');
const config = VISIT_CUSTOMER_TYPES[selectedType];
// Update button styles
$(".customer-type-btn").each(function() {
const btnType = $(this).data('type');
const btnConfig = VISIT_CUSTOMER_TYPES[btnType];
$(this).removeClass('active');
$(this).css({
'background-color': 'transparent',
'color': btnConfig.color
});
});
$(this).addClass('active');
$(this).css({
'background-color': config.color,
'color': 'white'
});
// Update time windows
minStartPicker.setDate(parseTimeToDateTime(config.windowStart).format(JSJoda.DateTimeFormatter.ofPattern('yyyy-M-d HH:mm')));
maxEndPicker.setDate(parseTimeToDateTime(config.windowEnd).format(JSJoda.DateTimeFormatter.ofPattern('yyyy-M-d HH:mm')));
// Update demand hint and value
$("#demandHint").text(`(${config.minDemand}-${config.maxDemand} typical)`);
$("#inputDemand").val(config.minDemand);
// Update service duration hint and value (use midpoint of range)
const avgService = Math.round((config.minService + config.maxService) / 2);
$("#durationHint").text(`(${config.minService}-${config.maxService} min typical)`);
$("#inputDuration").val(avgService);
});
const visitModalFooter = $("#newVisitModalFooter");
visitModalFooter.children().remove();
visitModalFooter.append("");
$("#recommendationButton").click(getRecommendationsModal);
}
function requestRecommendations(visitId, solution, endpointPath) {
$.post(endpointPath, JSON.stringify({solution, visitId}), function (recommendations) {
const visitModalContent = $("#newVisitModalContent");
visitModalContent.children().remove();
if (!recommendations || recommendations.length === 0) {
visitModalContent.append("No recommendations available. The recommendation API may not be fully implemented.
");
const visitModalFooter = $("#newVisitModalFooter");
visitModalFooter.children().remove();
visitModalFooter.append("");
return;
}
let visitOptions = "";
const visit = solution.visits.find(c => c.id === visitId);
recommendations.forEach((recommendation, index) => {
const scoreDiffDisplay = recommendation.scoreDiff || "N/A";
visitOptions += "" +
` ` +
` " +
"
";
});
visitModalContent.append(visitOptions);
const visitModalFooter = $("#newVisitModalFooter");
visitModalFooter.children().remove();
visitModalFooter.append("");
$("#applyRecommendationButton").click(_ => applyRecommendationModal(recommendations));
}).fail(function (xhr, ajaxOptions, thrownError) {
showError("Recommendations request failed.", xhr);
$('#newVisitModal').modal('hide');
});
}
function applyRecommendation(solution, visitId, vehicleId, index, endpointPath) {
$.post(endpointPath, JSON.stringify({solution, visitId, vehicleId, index}), function (updatedSolution) {
updateSolutionWithNewVisit(updatedSolution);
}).fail(function (xhr, ajaxOptions, thrownError) {
showError("Apply recommendation request failed.", xhr);
$('#newVisitModal').modal('hide');
});
}