Spaces:
Running
Running
| /* app-render-routes.js - route list rendering for the Bergamo FSR demo */ | |
| (function () { | |
| 'use strict'; | |
| var FSR = window.FSR = window.FSR || {}; | |
| var utils = FSR.utils; | |
| FSR.createRouteListRenderer = function (options) { | |
| var SF = options.SF; | |
| return { renderRouteCards: renderRouteCards }; | |
| function renderRouteCards(plan, routeGeometry) { | |
| var routeGeometryById = geometryByRouteId(routeGeometry); | |
| options.routeCards.innerHTML = ''; | |
| (plan.technician_routes || []).forEach(function (route, routeIdx) { | |
| var stats = utils.routeStats(plan, route); | |
| var routeId = routeKey(route, routeIdx); | |
| var focusedRouteId = options.getFocusedRouteId ? options.getFocusedRouteId() : null; | |
| var isFocused = focusedRouteId === routeId; | |
| var card = SF.el('div', { | |
| className: 'fsr-route-row' + (isFocused ? ' is-focused' : ''), | |
| role: 'button', | |
| tabIndex: 0, | |
| dataset: { routeId: routeId }, | |
| }); | |
| card.addEventListener('click', function () { focusRoute(routeId); }); | |
| card.addEventListener('keydown', function (event) { | |
| if (event.key === 'Enter' || event.key === ' ') { | |
| event.preventDefault(); | |
| focusRoute(routeId); | |
| } | |
| }); | |
| var top = SF.el('div', { className: 'fsr-route-row__top' }); | |
| top.appendChild(SF.el('strong', null, route.technician_name || route.id || ('Technician ' + (routeIdx + 1)))); | |
| top.appendChild(SF.el('span', { className: 'fsr-route-tag' }, String((route.visits || []).length) + ' stops')); | |
| card.appendChild(top); | |
| var meta = SF.el('div', { className: 'fsr-route-row__meta' }); | |
| meta.appendChild(SF.el('span', null, utils.formatDuration(stats.travelMinutes) + ' travel')); | |
| meta.appendChild(SF.el('span', null, utils.formatDuration(stats.serviceMinutes) + ' service')); | |
| meta.appendChild(SF.el('span', null, route.territory || 'no territory')); | |
| if (stats.lateMinutes) meta.appendChild(SF.el('span', null, utils.formatDuration(stats.lateMinutes) + ' late')); | |
| if (stats.overtimeMinutes) meta.appendChild(SF.el('span', null, utils.formatDuration(stats.overtimeMinutes) + ' overtime')); | |
| if (stats.unreachable || stats.missingSkills || stats.missingParts) { | |
| meta.appendChild(SF.el('span', null, String(stats.unreachable + stats.missingSkills + stats.missingParts) + ' hard issues')); | |
| } | |
| if (hasGeometryGaps(routeGeometryById[routeId])) meta.appendChild(SF.el('span', null, 'Geometry gaps')); | |
| card.appendChild(meta); | |
| var action = SF.createButton({ | |
| text: isFocused ? 'Show All' : 'Highlight', | |
| variant: isFocused ? 'default' : 'ghost', | |
| }); | |
| action.addEventListener('click', function (event) { | |
| event.stopPropagation(); | |
| focusRoute(routeId); | |
| }); | |
| card.appendChild(SF.el('div', { className: 'fsr-route-row__actions' }, action)); | |
| options.routeCards.appendChild(card); | |
| }); | |
| renderUnassignedCard(plan); | |
| } | |
| function renderUnassignedCard(plan) { | |
| var assigned = utils.assignedVisitSet(plan.technician_routes || []); | |
| var rows = (plan.service_visits || []).reduce(function (items, visit, idx) { | |
| if (assigned[idx]) return items; | |
| items.push([ | |
| visit.customer || visit.name || visit.id, | |
| utils.timeLabel(visit.earliest_minute) + '-' + utils.timeLabel(visit.latest_minute), | |
| utils.formatDuration(visit.duration_minutes || 0), | |
| ]); | |
| return items; | |
| }, []); | |
| if (!rows.length) return; | |
| var card = SF.el('div', { className: 'fsr-route-empty' }); | |
| card.appendChild(SF.el('strong', null, 'Unassigned visits')); | |
| card.appendChild(SF.createTable({ columns: ['Visit', 'Window', 'Duration'], rows: rows })); | |
| options.routeCards.appendChild(card); | |
| } | |
| function geometryByRouteId(routeGeometry) { | |
| return ((routeGeometry && routeGeometry.routes) || []).reduce(function (index, route) { | |
| index[String(route.routeId)] = route; | |
| return index; | |
| }, {}); | |
| } | |
| function hasGeometryGaps(routeGeometry) { | |
| return !!routeGeometry && (routeGeometry.segments || []).some(function (segment) { | |
| return segment.geometryStatus !== 'ROUTED'; | |
| }); | |
| } | |
| function focusRoute(routeId) { | |
| if (options.onFocusRoute) options.onFocusRoute(routeId); | |
| } | |
| function routeKey(route, idx) { | |
| return String(route.id || route.technician_name || ('route-' + idx)); | |
| } | |
| }; | |
| })(); | |