@fleetbase/fleetops-engine 0.6.44 → 0.6.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/addon/components/admin/map-settings.hbs +26 -0
  2. package/addon/components/admin/map-settings.js +46 -0
  3. package/addon/components/admin/routing-settings.hbs +11 -0
  4. package/addon/components/cell/order-route-type.hbs +46 -0
  5. package/addon/components/cell/order-route-type.js +90 -0
  6. package/addon/components/cell/place-address.hbs +1 -0
  7. package/addon/components/display-place.hbs +45 -56
  8. package/addon/components/display-place.js +21 -0
  9. package/addon/components/entity/form.hbs +24 -0
  10. package/addon/components/entity/form.js +30 -0
  11. package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  12. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  13. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  14. package/addon/components/leaflet-draw-control.js +1 -1
  15. package/addon/components/maintenance-schedule/form.js +2 -1
  16. package/addon/components/map/drawer/driver-listing.js +5 -5
  17. package/addon/components/map/drawer/geofence-event-listing.js +5 -2
  18. package/addon/components/map/drawer/place-listing.js +5 -5
  19. package/addon/components/map/drawer/position-listing.js +102 -21
  20. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  21. package/addon/components/map/google-live-map.hbs +9 -0
  22. package/addon/components/map/google-live-map.js +445 -0
  23. package/addon/components/map/leaflet-live-map.hbs +186 -142
  24. package/addon/components/map/leaflet-live-map.js +121 -41
  25. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  26. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  27. package/addon/components/map/toolbar.hbs +3 -3
  28. package/addon/components/map/toolbar.js +9 -1
  29. package/addon/components/modals/map-layer-form.hbs +132 -63
  30. package/addon/components/modals/orchestrator-import.hbs +4 -11
  31. package/addon/components/modals/orchestrator-import.js +17 -1
  32. package/addon/components/modals/update-order-activity.hbs +1 -1
  33. package/addon/components/orchestrator/order-pool.js +3 -2
  34. package/addon/components/orchestrator-workbench.js +29 -41
  35. package/addon/components/order/details/detail.js +2 -2
  36. package/addon/components/order/details/payload.hbs +5 -5
  37. package/addon/components/order/details/payload.js +18 -1
  38. package/addon/components/order/details/route.hbs +2 -2
  39. package/addon/components/order/details/route.js +76 -3
  40. package/addon/components/order/form/payload.hbs +33 -0
  41. package/addon/components/order/form/payload.js +1 -0
  42. package/addon/components/order/form/route.hbs +8 -8
  43. package/addon/components/order/form/route.js +78 -18
  44. package/addon/components/order/form/service-rate.hbs +1 -1
  45. package/addon/components/order/form.js +2 -2
  46. package/addon/components/order/route-editor.hbs +209 -147
  47. package/addon/components/order/route-editor.js +143 -102
  48. package/addon/components/place/address.hbs +1 -0
  49. package/addon/components/place/address.js +12 -0
  50. package/addon/components/route-list.hbs +43 -55
  51. package/addon/components/route-list.js +47 -0
  52. package/addon/components/route-optimization-wizard-panel.js +3 -4
  53. package/addon/components/service-rate/details.hbs +6 -6
  54. package/addon/components/service-rate/form.hbs +28 -15
  55. package/addon/components/vehicle/card.hbs +4 -4
  56. package/addon/controllers/maintenance/schedules/index.js +2 -1
  57. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  58. package/addon/controllers/operations/orders/index/details.js +113 -15
  59. package/addon/controllers/operations/orders/index/new.js +1 -1
  60. package/addon/controllers/operations/orders/index.js +8 -1
  61. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  62. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  63. package/addon/controllers/settings/map.js +97 -0
  64. package/addon/controllers/settings/routing.js +47 -12
  65. package/addon/extension.js +11 -0
  66. package/addon/helpers/f-to-int.js +6 -0
  67. package/addon/helpers/place-address-html.js +6 -0
  68. package/addon/instance-initializers/register-google-routes.js +17 -0
  69. package/addon/instance-initializers/register-osrm.js +5 -0
  70. package/addon/routes/application.js +9 -3
  71. package/addon/routes/operations/orchestrator.js +13 -0
  72. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  73. package/addon/routes/operations/orders/index/details.js +47 -24
  74. package/addon/routes/operations/orders/index/new.js +9 -0
  75. package/addon/routes/operations/orders/index.js +0 -1
  76. package/addon/routes/settings/map.js +3 -0
  77. package/addon/routes.js +1 -0
  78. package/addon/services/entity-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +4 -6
  80. package/addon/services/geofence-event-bus.js +2 -1
  81. package/addon/services/geofence.js +204 -168
  82. package/addon/services/google-routes.js +300 -0
  83. package/addon/services/leaflet-draw-restriction.js +2 -1
  84. package/addon/services/leaflet-map-manager.js +21 -0
  85. package/addon/services/maintenance-actions.js +4 -6
  86. package/addon/services/map-adapter/google.js +2205 -0
  87. package/addon/services/map-adapter/leaflet.js +1042 -0
  88. package/addon/services/map-adapter-interface.js +551 -0
  89. package/addon/services/map-manager.js +768 -0
  90. package/addon/services/map-settings.js +75 -0
  91. package/addon/services/movement-tracker.js +83 -32
  92. package/addon/services/order-actions.js +11 -1
  93. package/addon/services/osrm.js +79 -6
  94. package/addon/services/part-actions.js +4 -6
  95. package/addon/services/position-playback.js +135 -62
  96. package/addon/services/route-engine.js +83 -0
  97. package/addon/services/service-areas.js +8 -1
  98. package/addon/services/service-rate-actions.js +8 -3
  99. package/addon/styles/fleetops-engine.css +555 -0
  100. package/addon/templates/management/vehicles/index.hbs +0 -1
  101. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  102. package/addon/templates/operations/orders/index/details.hbs +0 -1
  103. package/addon/templates/settings/map.hbs +53 -0
  104. package/addon/templates/settings/routing.hbs +15 -4
  105. package/addon/utils/fleet-ops-options.js +34 -0
  106. package/addon/utils/has-valid-coordinates.js +6 -0
  107. package/addon/utils/map-provider-options.js +6 -0
  108. package/addon/utils/order-route-editing.js +86 -0
  109. package/addon/utils/order-route-summary.js +20 -0
  110. package/addon/utils/place-address-html.js +41 -0
  111. package/addon/utils/route-colors.js +43 -12
  112. package/addon/utils/route-visualization.js +93 -0
  113. package/addon/utils/serialize-payload.js +1 -1
  114. package/addon/utils/vendor-integration.js +2 -1
  115. package/app/components/admin/map-settings.js +1 -0
  116. package/app/components/admin/routing-settings.js +1 -0
  117. package/app/components/cell/order-route-type.js +1 -0
  118. package/app/components/cell/place-address.js +1 -0
  119. package/app/components/map/google-live-map.js +1 -0
  120. package/app/components/place/address.js +1 -0
  121. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  122. package/app/controllers/settings/map.js +1 -0
  123. package/app/helpers/f-to-int.js +1 -0
  124. package/app/helpers/place-address-html.js +1 -0
  125. package/app/routes/settings/map.js +1 -0
  126. package/app/services/map-adapter/google.js +1 -0
  127. package/app/services/map-adapter/leaflet.js +1 -0
  128. package/app/services/map-adapter-interface.js +1 -0
  129. package/app/services/map-manager.js +1 -0
  130. package/app/services/map-settings.js +1 -0
  131. package/app/templates/settings/map.js +1 -0
  132. package/app/utils/has-valid-coordinates.js +1 -0
  133. package/app/utils/order-route-editing.js +2 -0
  134. package/app/utils/order-route-summary.js +1 -0
  135. package/app/utils/place-address-html.js +1 -0
  136. package/app/utils/route-colors.js +1 -1
  137. package/app/utils/route-visualization.js +1 -0
  138. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  139. package/composer.json +1 -1
  140. package/extension.json +1 -1
  141. package/package.json +3 -3
  142. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  143. package/server/src/Http/Controllers/Api/v1/OrderController.php +36 -8
  144. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  145. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  146. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  147. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +7 -2
  148. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  149. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  150. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  151. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  152. package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
  153. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  154. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  155. package/server/src/Http/Filter/OrderFilter.php +2 -1
  156. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  157. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  158. package/server/src/Http/Resources/v1/Order.php +20 -2
  159. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  160. package/server/src/Models/Entity.php +7 -0
  161. package/server/src/Models/Order.php +81 -8
  162. package/server/src/Models/OrderConfig.php +15 -0
  163. package/server/src/Models/Payload.php +131 -27
  164. package/server/src/Models/Place.php +182 -48
  165. package/server/src/Models/ServiceRate.php +125 -32
  166. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  168. package/server/src/Rules/ComputableAlgo.php +1 -3
  169. package/server/src/Support/Algo.php +87 -1
  170. package/server/src/Support/Utils.php +69 -0
  171. package/server/src/routes.php +4 -0
  172. package/translations/en-us.yaml +1937 -1916
@@ -123,6 +123,39 @@ export const contactTypes = [
123
123
  { label: 'Dispatcher', value: 'dispatcher', description: 'Scheduling and operations contact' },
124
124
  ];
125
125
 
126
+ export const entityTypes = [
127
+ { label: 'Entity', value: 'entity', description: 'Standard generic entity representing an item.' },
128
+ { label: 'Parcel', value: 'parcel', description: 'Standard parcel or package used by parcel-based pricing.' },
129
+ { label: 'Package', value: 'package', description: 'Generic package or boxed item.' },
130
+ { label: 'Product', value: 'product', description: 'Sellable product or catalog item.' },
131
+ { label: 'Item', value: 'item', description: 'Generic inventory item when no stronger type applies.' },
132
+ { label: 'SKU / Inventory Unit', value: 'sku', description: 'Stock keeping unit or tracked inventory piece.' },
133
+ { label: 'Document', value: 'document', description: 'Envelopes, paperwork, and lightweight documents.' },
134
+ { label: 'Envelope', value: 'envelope', description: 'Flat document mailer or envelope.' },
135
+ { label: 'Box', value: 'box', description: 'Single boxed shipment unit.' },
136
+ { label: 'Carton', value: 'carton', description: 'Single carton, box, or case.' },
137
+ { label: 'Case', value: 'case', description: 'Case-packed goods or packaged case.' },
138
+ { label: 'Bundle', value: 'bundle', description: 'Bundled or strapped items shipped together.' },
139
+ { label: 'Bag', value: 'bag', description: 'Bagged goods, sacks, or pouches.' },
140
+ { label: 'Tote', value: 'tote', description: 'Reusable tote, bin, or container.' },
141
+ { label: 'Crate', value: 'crate', description: 'Rigid crate or caged shipment unit.' },
142
+ { label: 'Pallet', value: 'pallet', description: 'Palletized freight or grouped cargo.' },
143
+ { label: 'Container', value: 'container', description: 'Containerized shipment or storage container.' },
144
+ { label: 'Freight', value: 'freight', description: 'Loose freight or larger shipment unit.' },
145
+ { label: 'Cargo', value: 'cargo', description: 'General cargo not classified more specifically.' },
146
+ { label: 'Equipment', value: 'equipment', description: 'Tools, machines, or operational equipment.' },
147
+ { label: 'Machinery', value: 'machinery', description: 'Industrial machinery or large mechanical unit.' },
148
+ { label: 'Asset', value: 'asset', description: 'Tracked business asset or returnable item.' },
149
+ { label: 'Return', value: 'return', description: 'Returned goods or reverse-logistics item.' },
150
+ { label: 'Sample', value: 'sample', description: 'Sample item for trial, demo, or inspection.' },
151
+ { label: 'Spare Part', value: 'spare_part', description: 'Replacement component or service part.' },
152
+ { label: 'Medical Supply', value: 'medical_supply', description: 'Healthcare, lab, or medical delivery item.' },
153
+ { label: 'Food', value: 'food', description: 'Prepared food, meal, or grocery item.' },
154
+ { label: 'Beverage', value: 'beverage', description: 'Drink, liquid refreshment, or bottled shipment.' },
155
+ { label: 'Retail Goods', value: 'retail_goods', description: 'General consumer or retail merchandise.' },
156
+ { label: 'Passenger', value: 'passenger', description: 'Person transported as part of the order payload.' },
157
+ ];
158
+
126
159
  export const contactStatuses = [
127
160
  { label: 'Active', value: 'active', description: 'Contact is valid and in use' },
128
161
  { label: 'Inactive', value: 'inactive', description: 'Contact is no longer valid' },
@@ -821,6 +854,7 @@ export default function fleetOpsOptions(key) {
821
854
  fleetTypes,
822
855
  fleetStatuses,
823
856
  contactTypes,
857
+ entityTypes,
824
858
  contactStatuses,
825
859
  fuelReportTypes,
826
860
  fuelReportStatuses,
@@ -0,0 +1,6 @@
1
+ export default function hasValidCoordinates(place) {
2
+ const latitude = Number(place?.latitude);
3
+ const longitude = Number(place?.longitude);
4
+
5
+ return Number.isFinite(latitude) && Number.isFinite(longitude) && latitude !== 0 && longitude !== 0;
6
+ }
@@ -0,0 +1,6 @@
1
+ const MAP_PROVIDER_OPTIONS = [
2
+ { label: 'Leaflet', value: 'leaflet' },
3
+ { label: 'Google Maps', value: 'google' },
4
+ ];
5
+
6
+ export default MAP_PROVIDER_OPTIONS;
@@ -0,0 +1,86 @@
1
+ import { isArray } from '@ember/array';
2
+ import { Point } from '@fleetbase/fleetops-data/utils/geojson';
3
+ import { buildRoutePointsFromPayload, waypointToPlace } from './route-visualization';
4
+
5
+ export function routePointsToCoordinates(routePoints = []) {
6
+ return routePoints
7
+ .map(({ place }) => place)
8
+ .filter((place) => place?.hasValidCoordinates)
9
+ .map((place) => [place.latitude, place.longitude]);
10
+ }
11
+
12
+ export function getPayloadRouteCoordinates(payload) {
13
+ return routePointsToCoordinates(buildRoutePointsFromPayload(payload));
14
+ }
15
+
16
+ export function getPayloadIntermediateWaypoints(payload) {
17
+ const waypoints = payload?.waypoints;
18
+
19
+ if (typeof waypoints?.toArray === 'function') {
20
+ return waypoints.toArray();
21
+ }
22
+
23
+ return isArray(waypoints) ? waypoints : Array.from(waypoints ?? []);
24
+ }
25
+
26
+ export function hasRouteEndpoints(payload) {
27
+ return Boolean(payload?.pickup || payload?.dropoff);
28
+ }
29
+
30
+ export function canOptimizeIntermediateWaypoints(payload) {
31
+ return getPayloadIntermediateWaypoints(payload).length >= 2;
32
+ }
33
+
34
+ export function buildRouteOptimizationInput(order) {
35
+ const payload = order?.payload;
36
+ const routePoints = buildRoutePointsFromPayload(payload);
37
+ const coordinates = routePointsToCoordinates(routePoints).map(([lat, lng]) => [lng, lat]);
38
+ const waypoints = getPayloadIntermediateWaypoints(payload);
39
+
40
+ return {
41
+ order,
42
+ payload,
43
+ coordinates,
44
+ routePoints,
45
+ waypoints,
46
+ preserveEndpoints: hasRouteEndpoints(payload),
47
+ };
48
+ }
49
+
50
+ export function applyOptimizedIntermediateWaypoints(payload, sortedWaypoints = []) {
51
+ if (!payload) {
52
+ return;
53
+ }
54
+
55
+ if (typeof payload.setWaypoints === 'function') {
56
+ payload.setWaypoints(sortedWaypoints);
57
+ return;
58
+ }
59
+
60
+ payload.waypoints = sortedWaypoints;
61
+ }
62
+
63
+ export function createWaypointRecord(store, properties = {}) {
64
+ const place = properties.place ? waypointToPlace(properties.place) : null;
65
+ const serializedPlace = typeof place?.serialize === 'function' ? place.serialize() : {};
66
+
67
+ return store.createRecord('waypoint', {
68
+ type: 'dropoff',
69
+ ...serializedPlace,
70
+ ...properties,
71
+ place: place ?? null,
72
+ place_uuid: properties.place_uuid ?? place?.id ?? null,
73
+ location: properties.location ?? place?.location ?? new Point(0, 0),
74
+ });
75
+ }
76
+
77
+ export default {
78
+ routePointsToCoordinates,
79
+ getPayloadRouteCoordinates,
80
+ getPayloadIntermediateWaypoints,
81
+ hasRouteEndpoints,
82
+ canOptimizeIntermediateWaypoints,
83
+ buildRouteOptimizationInput,
84
+ applyOptimizedIntermediateWaypoints,
85
+ createWaypointRecord,
86
+ };
@@ -0,0 +1,20 @@
1
+ export function buildRouteTypeSummary({ hasIntermediateWaypoints = false, intermediateStopCount = 0, hasPickup = false, hasDropoff = false } = {}) {
2
+ const count = Math.max(Number(intermediateStopCount) || 0, 0);
3
+ const hasIntermediateStops = count > 0 || Boolean(hasIntermediateWaypoints);
4
+ const hasEndpoints = Boolean(hasPickup || hasDropoff);
5
+ const kind = hasIntermediateStops ? (hasEndpoints ? 'pickup_dropoff_stops' : 'multi_stop') : 'pickup_dropoff';
6
+
7
+ return {
8
+ kind,
9
+ intermediateStopCount: count,
10
+ icon: hasIntermediateStops ? 'route' : 'exchange-alt',
11
+ badgeClass: hasIntermediateStops ? 'import-preview-badge import-preview-badge--blue' : 'import-preview-badge import-preview-badge--gray',
12
+ translationKey:
13
+ kind === 'pickup_dropoff_stops'
14
+ ? 'orchestrator.col-preview-pickup-dropoff-stops'
15
+ : kind === 'multi_stop'
16
+ ? 'orchestrator.col-preview-multi-stop'
17
+ : 'orchestrator.col-preview-pickup-dropoff',
18
+ translationOptions: hasIntermediateStops ? { count } : {},
19
+ };
20
+ }
@@ -0,0 +1,41 @@
1
+ import { htmlSafe } from '@ember/template';
2
+ import { isBlank } from '@ember/utils';
3
+
4
+ function escapeHtml(value) {
5
+ return String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
6
+ }
7
+
8
+ function line(content, className = '') {
9
+ const classAttribute = className ? ` class="${className}"` : '';
10
+
11
+ return `<div${classAttribute}>${escapeHtml(content)}</div>`;
12
+ }
13
+
14
+ export default function placeAddressHtml(place) {
15
+ if (!place) {
16
+ return htmlSafe('');
17
+ }
18
+
19
+ const name = place.name === place.street1 ? null : place.name;
20
+ const cityStatePostalCode = [place.city, place.province, place.postal_code].filter((value) => !isBlank(value)).join(', ');
21
+ const lines = [];
22
+
23
+ if (name) {
24
+ lines.push(line(name, 'font-semibold'));
25
+ lines.push(line(place.street1));
26
+ } else if (place.street1) {
27
+ lines.push(line(place.street1, 'font-semibold'));
28
+ }
29
+
30
+ if (place.street2) {
31
+ lines.push(line(place.street2));
32
+ }
33
+
34
+ lines.push(line(cityStatePostalCode));
35
+
36
+ if (place.country) {
37
+ lines.push(line(place.country_name));
38
+ }
39
+
40
+ return htmlSafe(`<address class="uppercase truncate w-full">${lines.join('')}</address>`);
41
+ }
@@ -10,6 +10,15 @@
10
10
  * Colors are chosen for high contrast against both light and dark CartoDB tile layers.
11
11
  */
12
12
  export const ROUTE_COLOR_PALETTE = ['#0EA5E9', '#8B5CF6', '#F59E0B', '#10B981', '#F97316', '#EC4899', '#06B6D4', '#EF4444', '#84CC16', '#6366F1'];
13
+ export const ORDER_ROUTE_STATUS_COLORS = {
14
+ created: '#2563EB',
15
+ dispatched: '#4F46E5',
16
+ started: '#0891B2',
17
+ enroute: '#CA8A04',
18
+ driver_enroute: '#CA8A04',
19
+ completed: '#16A34A',
20
+ canceled: '#DC2626',
21
+ };
13
22
 
14
23
  /**
15
24
  * Darken a hex color by a given integer amount (0-255 per channel).
@@ -43,6 +52,23 @@ export function colorForId(id = '') {
43
52
  return ROUTE_COLOR_PALETTE[Math.abs(hash) % ROUTE_COLOR_PALETTE.length];
44
53
  }
45
54
 
55
+ export function normalizeOrderRouteStatus(status = 'created') {
56
+ switch (status) {
57
+ case 'pending':
58
+ return 'created';
59
+ case 'driver-enroute':
60
+ return 'driver_enroute';
61
+ case 'cancelled':
62
+ return 'canceled';
63
+ default:
64
+ return status && ORDER_ROUTE_STATUS_COLORS[status] ? status : 'created';
65
+ }
66
+ }
67
+
68
+ export function routeColorForStatus(status = 'created') {
69
+ return ORDER_ROUTE_STATUS_COLORS[normalizeOrderRouteStatus(status)] ?? ORDER_ROUTE_STATUS_COLORS.created;
70
+ }
71
+
46
72
  /**
47
73
  * Return a Leaflet Path options `styles` array for a route polyline
48
74
  * based on the order status. Uses a two-layer "cased" approach:
@@ -52,28 +78,33 @@ export function colorForId(id = '') {
52
78
  * @param {string} color - The base hex color for this route
53
79
  * @returns {Array<Object>} Array of Leaflet Path options objects
54
80
  */
55
- export function routeStyleForStatus(status, color) {
56
- const darker = darkenColor(color, 40);
57
-
58
- switch (status) {
59
- case 'pending':
60
- return [{ color: '#9CA3AF', weight: 5, opacity: 0.65, dashArray: '8 14', lineCap: 'round', lineJoin: 'round' }];
81
+ export function routeStyleForStatus(status, fallbackColor = null) {
82
+ const normalizedStatus = normalizeOrderRouteStatus(status);
83
+ const color = routeColorForStatus(normalizedStatus) ?? fallbackColor ?? ORDER_ROUTE_STATUS_COLORS.created;
84
+ const darker = darkenColor(color, 36);
61
85
 
86
+ switch (normalizedStatus) {
62
87
  case 'canceled':
63
- return [{ color: '#EF4444', weight: 4, opacity: 0.4, dashArray: '6 10', lineCap: 'round', lineJoin: 'round' }];
88
+ return [
89
+ { color: darkenColor(color, 18), weight: 5, opacity: 0.16, lineCap: 'round', lineJoin: 'round' },
90
+ { color, weight: 3, opacity: 0.52, lineCap: 'round', lineJoin: 'round' },
91
+ ];
64
92
 
65
93
  case 'completed':
66
94
  return [
67
- { color: darkenColor('#14B8A6', 30), weight: 7, opacity: 0.5, lineCap: 'round', lineJoin: 'round' },
68
- { color: '#14B8A6', weight: 4, opacity: 0.5, lineCap: 'round', lineJoin: 'round' },
95
+ { color: darker, weight: 6, opacity: 0.4, lineCap: 'round', lineJoin: 'round' },
96
+ { color, weight: 3, opacity: 0.8, lineCap: 'round', lineJoin: 'round' },
69
97
  ];
70
98
 
71
- case 'in_progress':
99
+ case 'created':
72
100
  case 'dispatched':
101
+ case 'started':
102
+ case 'enroute':
103
+ case 'driver_enroute':
73
104
  default:
74
105
  return [
75
- { color: darker, weight: 9, opacity: 0.9, lineCap: 'round', lineJoin: 'round' },
76
- { color: color, weight: 5, opacity: 1.0, lineCap: 'round', lineJoin: 'round' },
106
+ { color: darker, weight: 7, opacity: 0.82, lineCap: 'round', lineJoin: 'round' },
107
+ { color, weight: 4, opacity: 1, lineCap: 'round', lineJoin: 'round' },
77
108
  ];
78
109
  }
79
110
  }
@@ -0,0 +1,93 @@
1
+ import hasValidCoordinates from './has-valid-coordinates';
2
+
3
+ export function waypointToPlace(waypoint) {
4
+ if (!waypoint) {
5
+ return null;
6
+ }
7
+
8
+ return waypoint.place ?? waypoint;
9
+ }
10
+
11
+ export function buildPlaceAddressTooltip(title, place) {
12
+ if (!place) {
13
+ return title;
14
+ }
15
+
16
+ const name = place.name && place.name !== place.street1 ? place.name : null;
17
+ const cityStatePostalCode = [place.city, place.province, place.postal_code].filter(Boolean).join(', ');
18
+ const lines = [
19
+ name ? `<div class="fleetops-google-hover-tooltip__meta">${name}</div>` : '',
20
+ place.street1 ? `<div class="fleetops-google-hover-tooltip__meta">${place.street1}</div>` : '',
21
+ place.street2 ? `<div class="fleetops-google-hover-tooltip__meta">${place.street2}</div>` : '',
22
+ cityStatePostalCode ? `<div class="fleetops-google-hover-tooltip__meta">${cityStatePostalCode}</div>` : '',
23
+ place.country_name ? `<div class="fleetops-google-hover-tooltip__meta">${place.country_name}</div>` : '',
24
+ ].filter(Boolean);
25
+
26
+ return `
27
+ <div class="fleetops-google-hover-tooltip__title">${title}</div>
28
+ ${lines.join('')}
29
+ `;
30
+ }
31
+
32
+ export function buildRoutePointsFromPayload(payload) {
33
+ if (!payload) {
34
+ return [];
35
+ }
36
+
37
+ const points = [];
38
+ const waypoints = payload.waypoints ?? [];
39
+
40
+ if (hasValidCoordinates(payload.pickup)) {
41
+ points.push({ role: 'pickup', place: payload.pickup });
42
+ }
43
+
44
+ waypoints.forEach((waypoint, index) => {
45
+ const place = waypointToPlace(waypoint);
46
+
47
+ if (hasValidCoordinates(place)) {
48
+ points.push({ role: 'waypoint', place, stopNumber: index + 1 });
49
+ }
50
+ });
51
+
52
+ if (hasValidCoordinates(payload.dropoff)) {
53
+ points.push({ role: 'dropoff', place: payload.dropoff });
54
+ }
55
+
56
+ return points;
57
+ }
58
+
59
+ export function describeRoutePoint(routePoint, routeColor) {
60
+ const role = routePoint?.role;
61
+ const stopNumber = routePoint?.stopNumber ?? 1;
62
+ const label = role === 'pickup' ? 'P' : role === 'dropoff' ? 'D' : String(stopNumber);
63
+ const markerColor = role === 'pickup' ? '#22C55E' : role === 'dropoff' ? '#EF4444' : routeColor;
64
+ const title = role === 'pickup' ? 'Pickup' : role === 'dropoff' ? 'Dropoff' : `Stop ${stopNumber}`;
65
+
66
+ return {
67
+ label,
68
+ markerColor,
69
+ title,
70
+ };
71
+ }
72
+
73
+ export function buildRoutePointMarkerPresentation(routePoint, routeColor, options = {}) {
74
+ const role = routePoint?.role;
75
+ const place = routePoint?.place;
76
+ const { label, markerColor, title } = describeRoutePoint(routePoint, routeColor);
77
+ const zIndexOffset = role === 'pickup' ? 2200 : role === 'dropoff' ? 2100 : 1800;
78
+
79
+ return {
80
+ waypointLabel: label,
81
+ waypointColor: markerColor,
82
+ title,
83
+ zIndexOffset: options.zIndexOffset ?? zIndexOffset,
84
+ tooltip: buildPlaceAddressTooltip(title, place),
85
+ tooltipOptions: {
86
+ direction: 'top',
87
+ offset: [0, -20],
88
+ className: 'fleetops-waypoint-tooltip',
89
+ html: true,
90
+ ...(options.tooltipOptions ?? {}),
91
+ },
92
+ };
93
+ }
@@ -5,7 +5,7 @@ export default function serializePayload(payload) {
5
5
  const serialized = {
6
6
  pickup: serializeModel(payload.pickup),
7
7
  dropoff: serializeModel(payload.dropoff),
8
- entitities: serializeArray(payload.entities),
8
+ entities: serializeArray(payload.entities),
9
9
  waypoints: serializeArray(payload.waypoints),
10
10
  };
11
11
 
@@ -1,4 +1,5 @@
1
1
  import apiUrl from '@fleetbase/ember-core/utils/api-url';
2
+ import { isArray } from '@ember/array';
2
3
 
3
4
  /**
4
5
  * Normalize any array-like (EmberArray, Set, arguments) into a plain array.
@@ -8,7 +9,7 @@ import apiUrl from '@fleetbase/ember-core/utils/api-url';
8
9
  */
9
10
  export function normalizeToArray(input) {
10
11
  if (!input) return [];
11
- if (Array.isArray(input)) return input;
12
+ if (isArray(input)) return input;
12
13
  if (typeof input.toArray === 'function') return input.toArray();
13
14
  try {
14
15
  return Array.from(input);
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/admin/map-settings';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/admin/routing-settings';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/cell/order-route-type';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/cell/place-address';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/map/google-live-map';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/place/address';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/operations/orders/index/details/virtual';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/settings/map';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/helpers/f-to-int';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/helpers/place-address-html';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/routes/settings/map';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/map-adapter/google';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/map-adapter/leaflet';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/map-adapter-interface';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/map-manager';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/map-settings';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/templates/settings/map';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/utils/has-valid-coordinates';
@@ -0,0 +1,2 @@
1
+ export { default } from '@fleetbase/fleetops/utils/order-route-editing';
2
+ export * from '@fleetbase/fleetops/utils/order-route-editing';
@@ -0,0 +1 @@
1
+ export * from '@fleetbase/fleetops-engine/utils/order-route-summary';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/utils/place-address-html';
@@ -1 +1 @@
1
- export { default, ROUTE_COLOR_PALETTE, colorForId, darkenColor, routeStyleForStatus, waypointIconHtml } from '@fleetbase/fleetops-engine/utils/route-colors';
1
+ export * from '@fleetbase/fleetops-engine/utils/route-colors';
@@ -0,0 +1 @@
1
+ export * from '@fleetbase/fleetops-engine/utils/route-visualization';
@@ -0,0 +1,156 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+ xmlns:cc="http://creativecommons.org/ns#"
5
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+ xmlns:svg="http://www.w3.org/2000/svg"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xmlns:xlink="http://www.w3.org/1999/xlink"
9
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ viewBox="0 0 600 60"
12
+ height="60"
13
+ width="600"
14
+ id="svg4225"
15
+ version="1.1"
16
+ inkscape:version="0.91 r13725"
17
+ sodipodi:docname="spritesheet.svg"
18
+ inkscape:export-filename="/home/fpuga/development/upstream/icarto.Leaflet.draw/src/images/spritesheet-2x.png"
19
+ inkscape:export-xdpi="90"
20
+ inkscape:export-ydpi="90">
21
+ <metadata
22
+ id="metadata4258">
23
+ <rdf:RDF>
24
+ <cc:Work
25
+ rdf:about="">
26
+ <dc:format>image/svg+xml</dc:format>
27
+ <dc:type
28
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
29
+ <dc:title />
30
+ </cc:Work>
31
+ </rdf:RDF>
32
+ </metadata>
33
+ <defs
34
+ id="defs4256" />
35
+ <sodipodi:namedview
36
+ pagecolor="#ffffff"
37
+ bordercolor="#666666"
38
+ borderopacity="1"
39
+ objecttolerance="10"
40
+ gridtolerance="10"
41
+ guidetolerance="10"
42
+ inkscape:pageopacity="0"
43
+ inkscape:pageshadow="2"
44
+ inkscape:window-width="1920"
45
+ inkscape:window-height="1056"
46
+ id="namedview4254"
47
+ showgrid="false"
48
+ inkscape:zoom="1.3101852"
49
+ inkscape:cx="237.56928"
50
+ inkscape:cy="7.2419621"
51
+ inkscape:window-x="1920"
52
+ inkscape:window-y="24"
53
+ inkscape:window-maximized="1"
54
+ inkscape:current-layer="svg4225" />
55
+ <g
56
+ id="enabled"
57
+ style="fill:#464646;fill-opacity:1">
58
+ <g
59
+ id="polyline"
60
+ style="fill:#464646;fill-opacity:1">
61
+ <path
62
+ d="m 18,36 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z"
63
+ id="path4229"
64
+ inkscape:connector-curvature="0"
65
+ style="fill:#464646;fill-opacity:1" />
66
+ <path
67
+ d="m 36,18 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z"
68
+ id="path4231"
69
+ inkscape:connector-curvature="0"
70
+ style="fill:#464646;fill-opacity:1" />
71
+ <path
72
+ d="m 23.142,39.145 -2.285,-2.29 16,-15.998 2.285,2.285 z"
73
+ id="path4233"
74
+ inkscape:connector-curvature="0"
75
+ style="fill:#464646;fill-opacity:1" />
76
+ </g>
77
+ <path
78
+ id="polygon"
79
+ d="M 100,24.565 97.904,39.395 83.07,42 76,28.773 86.463,18 Z"
80
+ inkscape:connector-curvature="0"
81
+ style="fill:#464646;fill-opacity:1" />
82
+ <path
83
+ id="rectangle"
84
+ d="m 140,20 20,0 0,20 -20,0 z"
85
+ inkscape:connector-curvature="0"
86
+ style="fill:#464646;fill-opacity:1" />
87
+ <path
88
+ id="circle"
89
+ d="m 221,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
90
+ inkscape:connector-curvature="0"
91
+ style="fill:#464646;fill-opacity:1" />
92
+ <path
93
+ id="marker"
94
+ d="m 270,19 c -4.971,0 -9,4.029 -9,9 0,4.971 5.001,12 9,14 4.001,-2 9,-9.029 9,-14 0,-4.971 -4.029,-9 -9,-9 z m 0,12.5 c -2.484,0 -4.5,-2.014 -4.5,-4.5 0,-2.484 2.016,-4.5 4.5,-4.5 2.485,0 4.5,2.016 4.5,4.5 0,2.486 -2.015,4.5 -4.5,4.5 z"
95
+ inkscape:connector-curvature="0"
96
+ style="fill:#464646;fill-opacity:1" />
97
+ <g
98
+ id="edit"
99
+ style="fill:#464646;fill-opacity:1">
100
+ <path
101
+ d="m 337,30.156 0,0.407 0,5.604 c 0,1.658 -1.344,3 -3,3 l -10,0 c -1.655,0 -3,-1.342 -3,-3 l 0,-10 c 0,-1.657 1.345,-3 3,-3 l 6.345,0 3.19,-3.17 -9.535,0 c -3.313,0 -6,2.687 -6,6 l 0,10 c 0,3.313 2.687,6 6,6 l 10,0 c 3.314,0 6,-2.687 6,-6 l 0,-8.809 -3,2.968"
102
+ id="path4240"
103
+ inkscape:connector-curvature="0"
104
+ style="fill:#464646;fill-opacity:1" />
105
+ <path
106
+ d="m 338.72,24.637 -8.892,8.892 -2.828,0 0,-2.829 8.89,-8.89 z"
107
+ id="path4242"
108
+ inkscape:connector-curvature="0"
109
+ style="fill:#464646;fill-opacity:1" />
110
+ <path
111
+ d="m 338.697,17.826 4,0 0,4 -4,0 z"
112
+ transform="matrix(-0.70698336,-0.70723018,0.70723018,-0.70698336,567.55917,274.78273)"
113
+ id="path4244"
114
+ inkscape:connector-curvature="0"
115
+ style="fill:#464646;fill-opacity:1" />
116
+ </g>
117
+ <g
118
+ id="remove"
119
+ style="fill:#464646;fill-opacity:1">
120
+ <path
121
+ d="m 381,42 18,0 0,-18 -18,0 0,18 z m 14,-16 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z"
122
+ id="path4247"
123
+ inkscape:connector-curvature="0"
124
+ style="fill:#464646;fill-opacity:1" />
125
+ <path
126
+ d="m 395,20 0,-4 -10,0 0,4 -6,0 0,2 22,0 0,-2 -6,0 z m -2,0 -6,0 0,-2 6,0 0,2 z"
127
+ id="path4249"
128
+ inkscape:connector-curvature="0"
129
+ style="fill:#464646;fill-opacity:1" />
130
+ </g>
131
+ </g>
132
+ <g
133
+ id="disabled"
134
+ transform="translate(120,0)"
135
+ style="fill:#bbbbbb">
136
+ <use
137
+ xlink:href="#edit"
138
+ id="edit-disabled"
139
+ x="0"
140
+ y="0"
141
+ width="100%"
142
+ height="100%" />
143
+ <use
144
+ xlink:href="#remove"
145
+ id="remove-disabled"
146
+ x="0"
147
+ y="0"
148
+ width="100%"
149
+ height="100%" />
150
+ </g>
151
+ <path
152
+ style="fill:none;stroke:#464646;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
153
+ id="circle-3"
154
+ d="m 581.65725,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
155
+ inkscape:connector-curvature="0" />
156
+ </svg>