@fleetbase/fleetops-engine 0.6.37 → 0.6.39

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 (434) hide show
  1. package/addon/components/activity/form.hbs +2 -12
  2. package/addon/components/activity/logic-builder.hbs +1 -1
  3. package/addon/components/custom-entity/form.hbs +4 -20
  4. package/addon/components/device/form.hbs +3 -15
  5. package/addon/components/device/panel-header.hbs +13 -2
  6. package/addon/components/driver/details.hbs +33 -2
  7. package/addon/components/driver/form.hbs +42 -0
  8. package/addon/components/driver/panel-header.hbs +8 -1
  9. package/addon/components/driver/schedule.hbs +115 -76
  10. package/addon/components/driver/schedule.js +349 -157
  11. package/addon/components/driver-onboard-settings.hbs +2 -8
  12. package/addon/components/entity-field-editing-settings.hbs +2 -5
  13. package/addon/components/equipment/card.hbs +49 -0
  14. package/addon/components/equipment/card.js +6 -0
  15. package/addon/components/equipment/details.hbs +83 -44
  16. package/addon/components/equipment/form.hbs +111 -41
  17. package/addon/components/equipment/form.js +78 -10
  18. package/addon/components/equipment/panel-header.hbs +36 -0
  19. package/addon/components/equipment/panel-header.js +2 -0
  20. package/addon/components/fleet/driver-listing.hbs +3 -1
  21. package/addon/components/fleet/vehicle-listing.hbs +4 -7
  22. package/addon/components/fleet-panel/vehicle-listing.hbs +1 -6
  23. package/addon/components/fuel-report/form.hbs +1 -5
  24. package/addon/components/layout/fleet-ops-sidebar.hbs +40 -36
  25. package/addon/components/layout/fleet-ops-sidebar.js +61 -10
  26. package/addon/components/maintenance/cost-panel.hbs +176 -0
  27. package/addon/components/maintenance/cost-panel.js +241 -0
  28. package/addon/components/maintenance/details.hbs +123 -60
  29. package/addon/components/maintenance/form.hbs +138 -78
  30. package/addon/components/maintenance/form.js +131 -6
  31. package/addon/components/maintenance/panel-header.hbs +31 -0
  32. package/addon/components/maintenance/panel-header.js +2 -0
  33. package/addon/components/maintenance-schedule/details.hbs +260 -0
  34. package/addon/components/maintenance-schedule/details.js +158 -0
  35. package/addon/components/maintenance-schedule/form.hbs +287 -0
  36. package/addon/components/maintenance-schedule/form.js +199 -0
  37. package/addon/components/map/container.hbs +1 -1
  38. package/addon/components/map/drawer/device-event-listing.hbs +1 -1
  39. package/addon/components/map/drawer/driver-listing.hbs +1 -6
  40. package/addon/components/map/drawer/place-listing.hbs +1 -6
  41. package/addon/components/map/drawer/vehicle-listing.hbs +1 -6
  42. package/addon/components/map/drawer.hbs +8 -1
  43. package/addon/components/map/leaflet-live-map.hbs +2 -1
  44. package/addon/components/map/toolbar/visibility-control-panel.hbs +1 -1
  45. package/addon/components/map/toolbar.hbs +4 -29
  46. package/addon/components/modals/add-driver-shift.hbs +155 -0
  47. package/addon/components/modals/add-driver-shift.js +210 -0
  48. package/addon/components/modals/bulk-assign-orders.hbs +67 -0
  49. package/addon/components/modals/driver-shift.hbs +43 -0
  50. package/addon/components/modals/driver-shift.js +56 -0
  51. package/addon/components/modals/entity-form.hbs +5 -28
  52. package/addon/components/modals/orchestrator-import.hbs +351 -0
  53. package/addon/components/modals/orchestrator-import.js +807 -0
  54. package/addon/components/modals/order-config-new-status.hbs +1 -5
  55. package/addon/components/modals/scheduling-conflict.hbs +47 -0
  56. package/addon/components/modals/scheduling-conflict.js +53 -0
  57. package/addon/components/modals/send-work-order.hbs +91 -0
  58. package/addon/components/modals/send-work-order.js +3 -0
  59. package/addon/components/modals/service-area-form.hbs +1 -6
  60. package/addon/components/modals/set-driver-availability.hbs +50 -0
  61. package/addon/components/modals/set-driver-availability.js +57 -0
  62. package/addon/components/modals/update-order-activity.hbs +13 -9
  63. package/addon/components/modals/user-form.hbs +1 -5
  64. package/addon/components/modals/vehicle-form.hbs +17 -102
  65. package/addon/components/modals/vendor-form.hbs +15 -82
  66. package/addon/components/orchestrator/card-fields-settings.hbs +76 -0
  67. package/addon/components/orchestrator/card-fields-settings.js +134 -0
  68. package/addon/components/orchestrator/order-pool.hbs +264 -0
  69. package/addon/components/orchestrator/order-pool.js +394 -0
  70. package/addon/components/orchestrator/phase-builder.hbs +162 -0
  71. package/addon/components/orchestrator/phase-builder.js +162 -0
  72. package/addon/components/orchestrator/plan-viewer.hbs +278 -0
  73. package/addon/components/orchestrator/plan-viewer.js +342 -0
  74. package/addon/components/orchestrator/resource-panel.hbs +301 -0
  75. package/addon/components/orchestrator/resource-panel.js +106 -0
  76. package/addon/components/orchestrator-workbench.hbs +318 -0
  77. package/addon/components/orchestrator-workbench.js +1087 -0
  78. package/addon/components/order/details/custom-fields.hbs +10 -1
  79. package/addon/components/order/details/detail.hbs +37 -2
  80. package/addon/components/order/details/detail.js +0 -1
  81. package/addon/components/order/details/integrated-vendor-details.hbs +4 -4
  82. package/addon/components/order/details/notes.hbs +1 -7
  83. package/addon/components/order/details/route.hbs +1 -5
  84. package/addon/components/order/form/details.hbs +44 -10
  85. package/addon/components/order/form/details.js +57 -0
  86. package/addon/components/order/form/notes.hbs +1 -7
  87. package/addon/components/order/form/payload.hbs +1 -7
  88. package/addon/components/order/form/route.hbs +3 -15
  89. package/addon/components/order/header.hbs +1 -7
  90. package/addon/components/order/route-editor.hbs +4 -25
  91. package/addon/components/order/schedule-card.hbs +102 -95
  92. package/addon/components/order/schedule-card.js +8 -3
  93. package/addon/components/order-config-manager/details.hbs +2 -10
  94. package/addon/components/order-config-manager/entities.hbs +1 -7
  95. package/addon/components/order-config-manager.hbs +1 -6
  96. package/addon/components/part/card.hbs +49 -0
  97. package/addon/components/part/card.js +6 -0
  98. package/addon/components/part/details.hbs +102 -56
  99. package/addon/components/part/form.hbs +131 -56
  100. package/addon/components/part/form.js +78 -11
  101. package/addon/components/part/panel-header.hbs +36 -0
  102. package/addon/components/part/panel-header.js +2 -0
  103. package/addon/components/place/form.hbs +1 -7
  104. package/addon/components/sensor/details.hbs +1 -1
  105. package/addon/components/sensor/form.hbs +5 -3
  106. package/addon/components/sensor/panel-header.hbs +8 -1
  107. package/addon/components/service-area/form.hbs +1 -6
  108. package/addon/components/service-rate/form.hbs +12 -60
  109. package/addon/components/telematic/form.hbs +6 -2
  110. package/addon/components/vehicle/details/maintenance-history.hbs +42 -0
  111. package/addon/components/vehicle/details/maintenance-history.js +32 -0
  112. package/addon/components/vehicle/details/schedules.hbs +40 -0
  113. package/addon/components/vehicle/details/schedules.js +32 -0
  114. package/addon/components/vehicle/details/work-orders.hbs +42 -0
  115. package/addon/components/vehicle/details/work-orders.js +32 -0
  116. package/addon/components/vehicle/details.hbs +30 -0
  117. package/addon/components/vehicle/form.hbs +39 -0
  118. package/addon/components/vehicle/panel-header.hbs +19 -49
  119. package/addon/components/warranty/details.hbs +3 -2
  120. package/addon/components/warranty/form.hbs +3 -17
  121. package/addon/components/work-order/details.hbs +135 -40
  122. package/addon/components/work-order/form.hbs +178 -45
  123. package/addon/components/work-order/form.js +197 -4
  124. package/addon/components/work-order/panel-header.hbs +31 -0
  125. package/addon/components/work-order/panel-header.js +2 -0
  126. package/addon/controllers/connectivity/devices/index/details.js +1 -1
  127. package/addon/controllers/maintenance/equipment/index/details/index.js +0 -1
  128. package/addon/controllers/maintenance/equipment/index/details.js +36 -1
  129. package/addon/controllers/maintenance/equipment/index/edit.js +56 -1
  130. package/addon/controllers/maintenance/equipment/index/new.js +32 -1
  131. package/addon/controllers/maintenance/equipment/index.js +127 -113
  132. package/addon/controllers/maintenance/maintenances/index/details/index.js +3 -0
  133. package/addon/controllers/maintenance/maintenances/index/details.js +54 -0
  134. package/addon/controllers/maintenance/maintenances/index/edit.js +68 -0
  135. package/addon/controllers/maintenance/maintenances/index/new.js +34 -0
  136. package/addon/controllers/maintenance/maintenances/index.js +191 -0
  137. package/addon/controllers/maintenance/parts/index/details/index.js +0 -1
  138. package/addon/controllers/maintenance/parts/index/details.js +36 -1
  139. package/addon/controllers/maintenance/parts/index/edit.js +56 -1
  140. package/addon/controllers/maintenance/parts/index/new.js +32 -1
  141. package/addon/controllers/maintenance/parts/index.js +135 -113
  142. package/addon/controllers/maintenance/schedules/index/details.js +115 -0
  143. package/addon/controllers/maintenance/schedules/index/edit.js +41 -0
  144. package/addon/controllers/maintenance/schedules/index/new.js +33 -0
  145. package/addon/controllers/maintenance/schedules/index.js +280 -0
  146. package/addon/controllers/maintenance/work-orders/index/details.js +41 -1
  147. package/addon/controllers/maintenance/work-orders/index/edit.js +67 -1
  148. package/addon/controllers/maintenance/work-orders/index/new.js +43 -1
  149. package/addon/controllers/maintenance/work-orders/index.js +105 -113
  150. package/addon/controllers/management/drivers/index/details.js +6 -1
  151. package/addon/controllers/management/vehicles/index/details.js +65 -0
  152. package/addon/controllers/management/vehicles/index.js +18 -0
  153. package/addon/controllers/operations/orchestrator.js +10 -0
  154. package/addon/controllers/operations/orders/index/details.js +5 -0
  155. package/addon/controllers/operations/orders/index.js +6 -0
  156. package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
  157. package/addon/controllers/operations/scheduler/index.js +799 -275
  158. package/addon/controllers/operations/scheduler.js +21 -0
  159. package/addon/controllers/settings/orchestrator.js +70 -0
  160. package/addon/controllers/settings/scheduling.js +155 -0
  161. package/addon/extension.js +74 -1
  162. package/addon/instance-initializers/register-vroom-allocation.js +27 -0
  163. package/addon/models/maintenance-schedule.js +61 -0
  164. package/addon/routes/maintenance/equipment/index/details.js +27 -1
  165. package/addon/routes/maintenance/equipment/index/edit.js +27 -1
  166. package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
  167. package/addon/routes/maintenance/maintenances/index/details.js +29 -0
  168. package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
  169. package/addon/routes/maintenance/maintenances/index/new.js +3 -0
  170. package/addon/routes/maintenance/maintenances/index.js +23 -0
  171. package/addon/routes/maintenance/maintenances.js +3 -0
  172. package/addon/routes/maintenance/parts/index/details.js +27 -1
  173. package/addon/routes/maintenance/parts/index/edit.js +27 -1
  174. package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
  175. package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
  176. package/addon/routes/maintenance/schedules/index/details.js +25 -0
  177. package/addon/routes/maintenance/schedules/index/edit.js +25 -0
  178. package/addon/routes/maintenance/schedules/index/new.js +2 -0
  179. package/addon/routes/maintenance/schedules/index.js +21 -0
  180. package/addon/routes/maintenance/schedules.js +2 -0
  181. package/addon/routes/maintenance/work-orders/index/details.js +27 -1
  182. package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
  183. package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
  184. package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
  185. package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
  186. package/addon/routes/operations/orchestrator.js +23 -0
  187. package/addon/routes/operations/orders/index/details/virtual.js +23 -0
  188. package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
  189. package/addon/routes/operations/scheduler/index.js +48 -26
  190. package/addon/routes/operations/scheduler.js +14 -1
  191. package/addon/routes/settings/orchestrator.js +27 -0
  192. package/addon/routes/settings/scheduling.js +3 -0
  193. package/addon/routes.js +33 -1
  194. package/addon/services/driver-actions.js +40 -7
  195. package/addon/services/driver-scheduling.js +4 -1
  196. package/addon/services/equipment-actions.js +15 -5
  197. package/addon/services/leaflet-map-manager.js +14 -6
  198. package/addon/services/maintenance-actions.js +17 -14
  199. package/addon/services/maintenance-schedule-actions.js +118 -0
  200. package/addon/services/orchestration-engine-interface.js +49 -0
  201. package/addon/services/orchestration-engine.js +74 -0
  202. package/addon/services/order-actions.js +15 -0
  203. package/addon/services/order-allocation.js +116 -0
  204. package/addon/services/part-actions.js +12 -2
  205. package/addon/services/scheduling.js +316 -0
  206. package/addon/services/vehicle-actions.js +70 -7
  207. package/addon/services/vroom-allocation-engine.js +45 -0
  208. package/addon/services/work-order-actions.js +80 -0
  209. package/addon/styles/fleetops-engine.css +1658 -0
  210. package/addon/templates/analytics/reports/index/edit.hbs +1 -1
  211. package/addon/templates/analytics/reports/index/new.hbs +1 -1
  212. package/addon/templates/application.hbs +6 -1
  213. package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
  214. package/addon/templates/connectivity/devices.hbs +0 -1
  215. package/addon/templates/connectivity/events/index/details.hbs +0 -1
  216. package/addon/templates/connectivity/events.hbs +0 -1
  217. package/addon/templates/connectivity/sensors.hbs +0 -1
  218. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  219. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  220. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  221. package/addon/templates/connectivity/telematics.hbs +0 -1
  222. package/addon/templates/connectivity/tracking.hbs +0 -1
  223. package/addon/templates/connectivity.hbs +0 -1
  224. package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
  225. package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
  226. package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
  227. package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
  228. package/addon/templates/maintenance/equipment/index.hbs +48 -13
  229. package/addon/templates/maintenance/equipment.hbs +0 -1
  230. package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
  231. package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
  232. package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
  233. package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
  234. package/addon/templates/maintenance/maintenances/index.hbs +14 -0
  235. package/addon/templates/maintenance/maintenances.hbs +1 -0
  236. package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
  237. package/addon/templates/maintenance/parts/index/details.hbs +15 -2
  238. package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
  239. package/addon/templates/maintenance/parts/index/new.hbs +1 -2
  240. package/addon/templates/maintenance/parts/index.hbs +48 -13
  241. package/addon/templates/maintenance/parts.hbs +0 -1
  242. package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
  243. package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
  244. package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
  245. package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
  246. package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
  247. package/addon/templates/maintenance/schedules/index.hbs +40 -0
  248. package/addon/templates/maintenance/schedules.hbs +1 -0
  249. package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
  250. package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
  251. package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
  252. package/addon/templates/maintenance/work-orders.hbs +0 -1
  253. package/addon/templates/management/contacts/customers/edit.hbs +1 -2
  254. package/addon/templates/management/contacts/customers/new.hbs +1 -2
  255. package/addon/templates/management/contacts/customers.hbs +1 -1
  256. package/addon/templates/management/contacts/index/edit.hbs +1 -2
  257. package/addon/templates/management/contacts/index/new.hbs +1 -2
  258. package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
  259. package/addon/templates/management/drivers/index/edit.hbs +1 -2
  260. package/addon/templates/management/drivers/index/new.hbs +1 -2
  261. package/addon/templates/management/fleets/index/edit.hbs +1 -2
  262. package/addon/templates/management/fleets/index/new.hbs +1 -2
  263. package/addon/templates/management/fleets/index.hbs +1 -2
  264. package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
  265. package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
  266. package/addon/templates/management/fuel-reports/index.hbs +1 -2
  267. package/addon/templates/management/issues/index/edit.hbs +1 -2
  268. package/addon/templates/management/issues/index/new.hbs +1 -2
  269. package/addon/templates/management/issues/index.hbs +1 -2
  270. package/addon/templates/management/places/index/details/activity.hbs +0 -1
  271. package/addon/templates/management/places/index/details/comments.hbs +0 -1
  272. package/addon/templates/management/places/index/details/documents.hbs +0 -1
  273. package/addon/templates/management/places/index/details/map.hbs +0 -1
  274. package/addon/templates/management/places/index/details/operations.hbs +0 -1
  275. package/addon/templates/management/places/index/details/performance.hbs +0 -1
  276. package/addon/templates/management/places/index/details/rules.hbs +0 -1
  277. package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
  278. package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
  279. package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
  280. package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
  281. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  282. package/addon/templates/management/vehicles/index/edit.hbs +1 -2
  283. package/addon/templates/management/vehicles/index/new.hbs +1 -2
  284. package/addon/templates/management/vendors/index/edit.hbs +1 -2
  285. package/addon/templates/management/vendors/index/new.hbs +1 -2
  286. package/addon/templates/management/vendors/index.hbs +1 -2
  287. package/addon/templates/management/vendors/integrated.hbs +1 -2
  288. package/addon/templates/operations/orchestrator.hbs +1 -0
  289. package/addon/templates/operations/orders/index/details/virtual.hbs +1 -0
  290. package/addon/templates/operations/orders/index.hbs +6 -1
  291. package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
  292. package/addon/templates/operations/scheduler/index.hbs +147 -88
  293. package/addon/templates/operations/scheduler.hbs +7 -1
  294. package/addon/templates/settings/avatars.hbs +1 -1
  295. package/addon/templates/settings/orchestrator.hbs +65 -0
  296. package/addon/templates/settings/payments/index.hbs +1 -5
  297. package/addon/templates/settings/scheduling.hbs +82 -0
  298. package/addon/utils/create-full-calendar-event-from-order.js +52 -14
  299. package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
  300. package/addon/utils/fleet-ops-options.js +254 -0
  301. package/addon/utils/route-colors.js +99 -0
  302. package/addon/utils/to-calendar-date.js +70 -0
  303. package/app/components/driver/schedule.js +1 -0
  304. package/app/components/maintenance/cost-panel.js +1 -0
  305. package/app/components/maintenance/panel-header.js +1 -0
  306. package/app/components/maintenance-schedule/details.js +1 -0
  307. package/app/components/maintenance-schedule/form.js +1 -0
  308. package/app/components/modals/add-driver-shift.js +1 -0
  309. package/app/components/modals/bulk-assign-orders.js +1 -0
  310. package/app/components/modals/driver-shift.js +1 -0
  311. package/app/components/modals/orchestrator-import.js +1 -0
  312. package/app/components/modals/scheduling-conflict.js +1 -0
  313. package/app/components/modals/send-work-order.js +1 -0
  314. package/app/components/modals/set-driver-availability.js +1 -0
  315. package/app/components/orchestrator/card-fields-settings.js +1 -0
  316. package/app/components/orchestrator/order-pool.js +1 -0
  317. package/app/components/orchestrator/phase-builder.js +1 -0
  318. package/app/components/orchestrator/plan-viewer.js +1 -0
  319. package/app/components/orchestrator/resource-panel.js +1 -0
  320. package/app/components/orchestrator-workbench.js +1 -0
  321. package/app/components/vehicle/details/maintenance-history.js +1 -0
  322. package/app/components/vehicle/details/schedules.js +1 -0
  323. package/app/components/vehicle/details/work-orders.js +1 -0
  324. package/app/controllers/operations/orchestrator.js +1 -0
  325. package/app/controllers/settings/orchestrator.js +1 -0
  326. package/app/controllers/settings/scheduling.js +1 -0
  327. package/app/routes/operations/orchestrator.js +1 -0
  328. package/app/routes/operations/orders/index/details/virtual.js +1 -0
  329. package/app/routes/settings/orchestrator.js +1 -0
  330. package/app/routes/settings/scheduling.js +1 -0
  331. package/app/services/maintenance-schedule-actions.js +1 -0
  332. package/app/services/orchestration-engine-interface.js +1 -0
  333. package/app/services/orchestration-engine.js +1 -0
  334. package/app/services/order-allocation.js +1 -0
  335. package/app/services/scheduling.js +1 -0
  336. package/app/services/vroom-allocation-engine.js +1 -0
  337. package/app/templates/operations/orders/index/details/virtual.js +1 -0
  338. package/app/templates/settings/scheduling.js +1 -0
  339. package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
  340. package/app/utils/route-colors.js +1 -0
  341. package/composer.json +5 -3
  342. package/extension.json +1 -1
  343. package/package.json +6 -5
  344. package/server/config/fleetops.php +20 -1
  345. package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
  346. package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
  347. package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
  348. package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
  349. package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
  350. package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
  351. package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
  352. package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
  353. package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +58 -0
  354. package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
  355. package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
  356. package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +41 -0
  357. package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
  358. package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
  359. package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
  360. package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
  361. package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
  362. package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
  363. package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
  364. package/server/src/Auth/Schemas/FleetOps.php +44 -0
  365. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
  366. package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
  367. package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
  368. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
  369. package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
  370. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
  371. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
  372. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
  373. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
  374. package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
  375. package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
  376. package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
  377. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
  378. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
  379. package/server/src/Http/Resources/v1/Driver.php +1 -0
  380. package/server/src/Http/Resources/v1/Maintenance.php +138 -0
  381. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
  382. package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
  383. package/server/src/Http/Resources/v1/Order.php +7 -4
  384. package/server/src/Http/Resources/v1/Waypoint.php +7 -0
  385. package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
  386. package/server/src/Imports/EquipmentImport.php +32 -0
  387. package/server/src/Imports/MaintenanceImport.php +32 -0
  388. package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
  389. package/server/src/Imports/PartImport.php +32 -0
  390. package/server/src/Imports/WorkOrderImport.php +32 -0
  391. package/server/src/Jobs/ProcessAllocationJob.php +119 -0
  392. package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
  393. package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
  394. package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
  395. package/server/src/Mail/WorkOrderDispatched.php +58 -0
  396. package/server/src/Models/Asset.php +2 -2
  397. package/server/src/Models/Device.php +1 -1
  398. package/server/src/Models/Driver.php +82 -4
  399. package/server/src/Models/Equipment.php +62 -2
  400. package/server/src/Models/Maintenance.php +127 -9
  401. package/server/src/Models/MaintenanceSchedule.php +353 -0
  402. package/server/src/Models/Manifest.php +214 -0
  403. package/server/src/Models/ManifestStop.php +162 -0
  404. package/server/src/Models/Order.php +70 -0
  405. package/server/src/Models/OrderConfig.php +5 -2
  406. package/server/src/Models/Part.php +69 -3
  407. package/server/src/Models/Payload.php +7 -2
  408. package/server/src/Models/Place.php +1 -1
  409. package/server/src/Models/Sensor.php +1 -1
  410. package/server/src/Models/ServiceQuote.php +1 -1
  411. package/server/src/Models/Vehicle.php +20 -1
  412. package/server/src/Models/Warranty.php +1 -1
  413. package/server/src/Models/Waypoint.php +7 -1
  414. package/server/src/Models/WorkOrder.php +122 -12
  415. package/server/src/Notifications/DriverShiftChanged.php +110 -0
  416. package/server/src/Observers/WorkOrderObserver.php +107 -0
  417. package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
  418. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
  419. package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
  420. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
  421. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
  422. package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
  423. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +290 -0
  424. package/server/src/Providers/EventServiceProvider.php +7 -1
  425. package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
  426. package/server/src/routes.php +65 -4
  427. package/translations/ar-ae.yml +44 -12
  428. package/translations/bg-bg.yaml +51 -10
  429. package/translations/en-us.yaml +444 -1
  430. package/translations/fr-fr.yaml +51 -10
  431. package/translations/mn-mn.yaml +51 -10
  432. package/translations/pt-br.yaml +51 -10
  433. package/translations/ru-ru.yaml +51 -10
  434. package/translations/vi-vn.yaml +48 -12
@@ -0,0 +1,342 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { action } from '@ember/object';
4
+ import { inject as service } from '@ember/service';
5
+ import { addMinutes } from 'date-fns';
6
+ import toCalendarDate from '../../utils/to-calendar-date';
7
+
8
+ /**
9
+ * Orchestrator::PlanViewer
10
+ *
11
+ * Displays the proposed plan after a run completes. Shows per-vehicle
12
+ * route cards with ordered stop rows. Supports drag-and-drop manual
13
+ * overrides and expand/collapse per route card.
14
+ *
15
+ * Tabs:
16
+ * - Routes — per-vehicle route cards with ordered stop list
17
+ * - Timeline — EventCalendar resourceTimelineDay view (same library as Scheduler)
18
+ *
19
+ * @arg planByVehicle - Array of { vehicle, driver, orders, routeColor, summary }
20
+ * @arg unassignedAfterRun - Array of orders the engine could not assign
21
+ * @arg runMessage - Optional informational message from the engine
22
+ * @arg onDropOnVehicle - Action(vehicleId, driverId, event)
23
+ * @arg onDragOver - Action(event)
24
+ * @arg onDismissMessage - Action — dismiss the run message banner
25
+ * @arg formatDuration - Helper(seconds) → string
26
+ * @arg formatDistance - Helper(metres) → string
27
+ * @arg formatUnixTime - Helper(unix) → string
28
+ */
29
+ export default class OrchestratorPlanViewerComponent extends Component {
30
+ @service currentUser;
31
+
32
+ @tracked expandedCards = new Set();
33
+ @tracked activeTab = 'routes'; // 'routes' | 'timeline'
34
+ @tracked _calendar = null;
35
+
36
+ // ── Tab / card actions ────────────────────────────────────────────────────
37
+
38
+ @action setTab(tab) {
39
+ this.activeTab = tab;
40
+ }
41
+
42
+ @action toggleCard(vehicleId) {
43
+ const expanded = new Set(this.expandedCards);
44
+ if (expanded.has(vehicleId)) {
45
+ expanded.delete(vehicleId);
46
+ } else {
47
+ expanded.add(vehicleId);
48
+ }
49
+ this.expandedCards = expanded;
50
+ }
51
+
52
+ @action expandAll() {
53
+ const ids = (this.args.planByVehicle ?? []).map((g) => g.vehicle?.public_id).filter(Boolean);
54
+ this.expandedCards = new Set(ids);
55
+ }
56
+
57
+ @action collapseAll() {
58
+ this.expandedCards = new Set();
59
+ }
60
+
61
+ /**
62
+ * Returns true when every vehicle card is expanded.
63
+ * Used to show the correct label on the single toggle button.
64
+ */
65
+ get allExpanded() {
66
+ const ids = (this.args.planByVehicle ?? []).map((g) => g.vehicle?.public_id).filter(Boolean);
67
+ return ids.length > 0 && ids.every((id) => this.expandedCards.has(id));
68
+ }
69
+
70
+ @action toggleExpandAll() {
71
+ if (this.allExpanded) {
72
+ this.collapseAll();
73
+ } else {
74
+ this.expandAll();
75
+ }
76
+ }
77
+
78
+ /**
79
+ * isExpanded — decorated as @action so Glimmer allows it to be invoked
80
+ * with an argument from HBS: (this.isExpanded vehicleId)
81
+ */
82
+ @action isExpanded(vehicleId) {
83
+ return this.expandedCards.has(vehicleId);
84
+ }
85
+
86
+ get hasUnassigned() {
87
+ return (this.args.unassignedAfterRun ?? []).length > 0;
88
+ }
89
+
90
+ get totalOrders() {
91
+ return (this.args.planByVehicle ?? []).reduce((sum, g) => sum + (g.orders?.length ?? 0), 0);
92
+ }
93
+
94
+ get totalVehicles() {
95
+ return (this.args.planByVehicle ?? []).length;
96
+ }
97
+
98
+ // ── EventCalendar ─────────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Returns the IANA timezone string for the current organisation.
102
+ * Falls back to the browser's local timezone.
103
+ */
104
+ get companyTimezone() {
105
+ return this.currentUser?.company?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
106
+ }
107
+
108
+ /**
109
+ * Receives the EventCalendar instance once it is mounted.
110
+ */
111
+ @action setCalendarApi(calendar) {
112
+ this._calendar = calendar;
113
+ }
114
+
115
+ /**
116
+ * The date to display on the timeline — derived from the earliest scheduled
117
+ * order in the plan, so the calendar opens on the right day automatically.
118
+ */
119
+ get timelineDate() {
120
+ const plan = this.args.planByVehicle ?? [];
121
+ let earliest = null;
122
+ for (const group of plan) {
123
+ for (const item of group.orders ?? []) {
124
+ const t = this._resolveStopTime(item);
125
+ if (t !== null) {
126
+ if (earliest === null || t < earliest) earliest = t;
127
+ }
128
+ }
129
+ }
130
+ if (earliest === null) return new Date();
131
+ // Convert the UTC unix timestamp to a wall-clock Date in the company timezone.
132
+ return toCalendarDate(new Date(earliest * 1000), this.companyTimezone);
133
+ }
134
+
135
+ /**
136
+ * Resources for EventCalendar — one row per vehicle/driver group.
137
+ * When a driver phase ran (@hasDriverPhase), the row title uses the driver
138
+ * name as the primary identifier; otherwise the vehicle name is used.
139
+ */
140
+ get calendarResources() {
141
+ const hasDriverPhase = this.args.hasDriverPhase ?? false;
142
+ return (this.args.planByVehicle ?? []).map((group) => {
143
+ const primaryTitle =
144
+ hasDriverPhase && group.driver ? (group.driver.name ?? group.driver.display_name ?? 'Driver') : (group.vehicle?.display_name ?? group.vehicle?.name ?? 'Vehicle');
145
+ return {
146
+ id: group.vehicle?.public_id ?? group.vehicle?.id ?? String(Math.random()),
147
+ title: primaryTitle,
148
+ extendedProps: { group, hasDriverPhase },
149
+ };
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Events for EventCalendar — one event per planned stop.
155
+ * Uses the vehicle's routeColor for consistent color coding.
156
+ */
157
+ get calendarEvents() {
158
+ const tz = this.companyTimezone;
159
+ const events = [];
160
+ const DURATION_MIN = 30; // default slot duration when no estimate available
161
+
162
+ for (const group of this.args.planByVehicle ?? []) {
163
+ const resourceId = group.vehicle?.public_id ?? group.vehicle?.id;
164
+ const color = group.routeColor ?? '#6366f1';
165
+
166
+ // Cursor used when no arrival time is known — starts at 08:00 on the plan date
167
+ let cursorMs = null;
168
+
169
+ for (const item of group.orders ?? []) {
170
+ const stopTimeSec = this._resolveStopTime(item);
171
+ let startMs;
172
+
173
+ if (stopTimeSec !== null) {
174
+ startMs = stopTimeSec * 1000;
175
+ cursorMs = startMs + DURATION_MIN * 60 * 1000;
176
+ } else {
177
+ // No time known — use cursor or default to 08:00 today
178
+ if (cursorMs === null) {
179
+ const today = new Date();
180
+ today.setHours(8, 0, 0, 0);
181
+ cursorMs = today.getTime();
182
+ }
183
+ startMs = cursorMs;
184
+ cursorMs = startMs + DURATION_MIN * 60 * 1000;
185
+ }
186
+
187
+ const startUtc = new Date(startMs);
188
+ const endUtc = addMinutes(startUtc, DURATION_MIN);
189
+
190
+ // Convert to "fake local" dates for EventCalendar timezone handling
191
+ const start = toCalendarDate(startUtc, tz);
192
+ const end = toCalendarDate(endUtc, tz);
193
+
194
+ const order = item.order;
195
+ const tracking = order?.tracking ?? order?.public_id ?? '—';
196
+ const customerName = order?.customer_name ?? null;
197
+ const pickupAddress = order?.payload?.pickup?.address ?? order?.pickup_name ?? null;
198
+ const dropoffAddress = order?.payload?.dropoff?.address ?? order?.dropoff_name ?? null;
199
+ const sequence = item.sequence ?? null;
200
+
201
+ events.push({
202
+ id: `plan-${resourceId}-${order?.public_id ?? Math.random()}`,
203
+ resourceId,
204
+ title: tracking,
205
+ start,
206
+ end,
207
+ display: 'block',
208
+ backgroundColor: color,
209
+ borderColor: color,
210
+ textColor: '#ffffff',
211
+ extendedProps: {
212
+ order,
213
+ tracking,
214
+ sequence,
215
+ customerName,
216
+ pickupAddress,
217
+ dropoffAddress,
218
+ startUtc,
219
+ endUtc,
220
+ },
221
+ });
222
+ }
223
+ }
224
+
225
+ return events;
226
+ }
227
+
228
+ /**
229
+ * Renders the resource label cell for each timeline row.
230
+ * When a driver phase ran (hasDriverPhase), the driver name is the primary
231
+ * label and the vehicle name is the secondary sub-label. Otherwise the
232
+ * vehicle name is primary and the driver name (if any) is secondary.
233
+ */
234
+ @action renderResourceLabel({ resource }) {
235
+ const { group, hasDriverPhase } = resource.extendedProps ?? {};
236
+ const vehicleName = group?.vehicle?.display_name ?? group?.vehicle?.name ?? 'Vehicle';
237
+ const driverName = group?.driver?.name ?? '';
238
+ const stopCount = group?.orders?.length ?? 0;
239
+ const color = group?.routeColor ?? '#6366f1';
240
+
241
+ const primaryLabel = hasDriverPhase && driverName ? driverName : vehicleName;
242
+ const secondaryLabel = hasDriverPhase && driverName ? vehicleName : driverName;
243
+
244
+ return {
245
+ html: `<div style="width:100%;box-sizing:border-box;padding:4px 8px;border-left:3px solid ${color};">
246
+ <div style="font-size:0.72rem;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%;">${primaryLabel}</div>
247
+ ${secondaryLabel ? `<div style="font-size:0.65rem;color:#9ca3af;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${secondaryLabel}</div>` : ''}
248
+ <div style="font-size:0.62rem;color:#6b7280;margin-top:1px;">${stopCount} stop${stopCount !== 1 ? 's' : ''}</div>
249
+ </div>`,
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Renders the event tile content inside the timeline.
255
+ * Shows: sequence + tracking number, scheduled time, pickup → dropoff address, customer.
256
+ */
257
+ @action renderEventContent({ event }) {
258
+ const { tracking, sequence, customerName, pickupAddress, dropoffAddress, startUtc, endUtc } = event.extendedProps ?? {};
259
+
260
+ const fmt = (d) => {
261
+ if (!d) return '';
262
+ return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
263
+ };
264
+
265
+ const title = sequence != null ? `${sequence}. ${tracking}` : tracking;
266
+ const timeRange = startUtc ? `${fmt(startUtc)} – ${fmt(endUtc)}` : '';
267
+ const routeLine = [pickupAddress, dropoffAddress].filter(Boolean).join(' > ');
268
+
269
+ return {
270
+ html: `<div style="display:flex;flex-direction:column;gap:2px;padding:3px 0;overflow:hidden;height:100%;">
271
+ <div style="display:flex;align-items:center;gap:4px;flex-wrap:nowrap;">
272
+ <span style="width:7px;height:7px;border-radius:50%;background:#ffffff;opacity:0.9;flex-shrink:0;"></span>
273
+ <span style="font-size:0.72rem;font-weight:700;color:#ffffff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;">${title}</span>
274
+ </div>
275
+ ${timeRange ? `<div style="font-size:0.65rem;color:rgba(255,255,255,0.9);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3;">${timeRange}</div>` : ''}
276
+ ${
277
+ routeLine
278
+ ? `<div style="font-size:0.62rem;color:rgba(255,255,255,0.8);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.3;word-break:break-word;">${routeLine}</div>`
279
+ : ''
280
+ }
281
+ ${
282
+ customerName
283
+ ? `<div style="font-size:0.62rem;color:rgba(255,255,255,0.7);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3;">${customerName}</div>`
284
+ : ''
285
+ }
286
+ </div>`,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Calendar display options — 24h time format, no header toolbar
292
+ * (navigation is not needed for a read-only plan review).
293
+ */
294
+ get calendarOptions() {
295
+ return {
296
+ slotLabelFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
297
+ eventTimeFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
298
+ dayHeaderFormat: { weekday: 'short', month: 'numeric', day: 'numeric' },
299
+ };
300
+ }
301
+
302
+ get calendarHeaderToolbar() {
303
+ return { start: '', center: 'title', end: '' };
304
+ }
305
+
306
+ // ── Internal helpers ──────────────────────────────────────────────────────
307
+
308
+ /**
309
+ * Resolve the best available Unix timestamp (seconds) for a stop item.
310
+ *
311
+ * Priority:
312
+ * 1. VROOM arrival time (item.arrival — Unix seconds)
313
+ * 2. order.scheduled_at (JS Date object from Ember Data or ISO string from plain JSON)
314
+ * 3. null — caller must use a fallback cursor
315
+ */
316
+ _resolveStopTime(item) {
317
+ // 1. VROOM arrival time (Unix seconds integer)
318
+ if (item.arrival && typeof item.arrival === 'number') {
319
+ return item.arrival;
320
+ }
321
+ // 2. order.scheduled_at — may be a Date object (Ember Data) or an ISO
322
+ // string (plain JSON from orchestrator/orders endpoint)
323
+ const scheduledAt = item.order?.scheduled_at;
324
+ if (scheduledAt) {
325
+ const d = scheduledAt instanceof Date ? scheduledAt : new Date(scheduledAt);
326
+ if (!isNaN(d.getTime())) {
327
+ return Math.floor(d.getTime() / 1000);
328
+ }
329
+ }
330
+ return null;
331
+ }
332
+
333
+ /**
334
+ * The date range covered by the plan, formatted for the header.
335
+ * e.g. "Thu Apr 10, 2026"
336
+ */
337
+ get timelineDateRange() {
338
+ const date = this.timelineDate;
339
+ if (!date) return null;
340
+ return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' });
341
+ }
342
+ }
@@ -0,0 +1,301 @@
1
+ {{! Orchestrator::ResourcePanel — right panel: Vehicles + Drivers tabs }}
2
+ <div class="resource-panel flex flex-col h-full overflow-hidden">
3
+
4
+ {{! Tab bar — matches original border-bottom underline style }}
5
+ <div class="flex border-b dark:border-gray-700 flex-shrink-0">
6
+ <button
7
+ type="button"
8
+ class="flex-1 py-2 pl-2 text-xs font-medium transition-colors
9
+ {{if
10
+ (eq this.activeTab 'vehicles')
11
+ 'text-indigo-500 border-b-2 border-indigo-500 dark:text-indigo-400 dark:border-indigo-400'
12
+ 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
13
+ }}"
14
+ {{on "click" (fn this.setTab "vehicles")}}
15
+ >
16
+ <FaIcon @icon="truck" @size="xs" class="mr-1" />
17
+ {{t "orchestrator.available-vehicles"}}
18
+ <span class="ml-1 text-gray-400 tabular-nums">({{this.filteredVehicles.length}})</span>
19
+ </button>
20
+ <button
21
+ type="button"
22
+ class="flex-1 py-2 text-xs font-medium transition-colors
23
+ {{if
24
+ (eq this.activeTab 'drivers')
25
+ 'text-indigo-500 border-b-2 border-indigo-500 dark:text-indigo-400 dark:border-indigo-400'
26
+ 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
27
+ }}"
28
+ {{on "click" (fn this.setTab "drivers")}}
29
+ >
30
+ <FaIcon @icon="id-card-alt" @size="xs" class="mr-1" />
31
+ {{t "orchestrator.available-drivers"}}
32
+ <span class="ml-1 text-gray-400 tabular-nums">({{this.filteredDrivers.length}})</span>
33
+ </button>
34
+ </div>
35
+
36
+ {{! Search + filter strip — shared, sits above the scrollable list }}
37
+ <div class="px-2 py-1.5 border-b dark:border-gray-700 flex-shrink-0 space-y-1">
38
+ {{! Search input }}
39
+ <div class="relative">
40
+ <FaIcon @icon="search" @size="xs" class="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none" />
41
+ {{#if (eq this.activeTab "vehicles")}}
42
+ <input
43
+ type="text"
44
+ class="form-input form-input-sm w-full pl-7i pr-2 py-1 text-xs"
45
+ placeholder={{t "orchestrator.search-vehicles"}}
46
+ value={{this.vehicleSearch}}
47
+ {{on "input" this.onVehicleSearchInput}}
48
+ />
49
+ {{else}}
50
+ <input
51
+ type="text"
52
+ class="form-input form-input-sm w-full pl-7i pr-2 py-1 text-xs"
53
+ placeholder={{t "orchestrator.search-drivers"}}
54
+ value={{this.driverSearch}}
55
+ {{on "input" this.onDriverSearchInput}}
56
+ />
57
+ {{/if}}
58
+ </div>
59
+ {{! Filter chips }}
60
+ {{#if (eq this.activeTab "vehicles")}}
61
+ <div class="flex gap-1 flex-wrap">
62
+ <button
63
+ type="button"
64
+ class="px-2 py-0.5 rounded text-xs transition-colors
65
+ {{if
66
+ (eq this.vehicleFilter 'all')
67
+ 'bg-indigo-500 text-white'
68
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
69
+ }}"
70
+ {{on "click" (fn this.setVehicleFilter "all")}}
71
+ >{{t "orchestrator.filter-all"}}</button>
72
+ <button
73
+ type="button"
74
+ class="px-2 py-0.5 rounded text-xs transition-colors
75
+ {{if
76
+ (eq this.vehicleFilter 'active')
77
+ 'bg-indigo-500 text-white'
78
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
79
+ }}"
80
+ {{on "click" (fn this.setVehicleFilter "active")}}
81
+ >{{t "orchestrator.filter-active"}}</button>
82
+ <button
83
+ type="button"
84
+ class="px-2 py-0.5 rounded text-xs transition-colors
85
+ {{if
86
+ (eq this.vehicleFilter 'available')
87
+ 'bg-indigo-500 text-white'
88
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
89
+ }}"
90
+ {{on "click" (fn this.setVehicleFilter "available")}}
91
+ >{{t "orchestrator.filter-available"}}</button>
92
+ <button
93
+ type="button"
94
+ class="px-2 py-0.5 rounded text-xs transition-colors
95
+ {{if
96
+ (eq this.vehicleFilter 'no-driver')
97
+ 'bg-indigo-500 text-white'
98
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
99
+ }}"
100
+ {{on "click" (fn this.setVehicleFilter "no-driver")}}
101
+ >{{t "orchestrator.filter-no-driver"}}</button>
102
+ </div>
103
+ {{else}}
104
+ <div class="flex gap-1 flex-wrap">
105
+ <button
106
+ type="button"
107
+ class="px-2 py-0.5 rounded text-xs transition-colors
108
+ {{if
109
+ (eq this.driverFilter 'all')
110
+ 'bg-indigo-500 text-white'
111
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
112
+ }}"
113
+ {{on "click" (fn this.setDriverFilter "all")}}
114
+ >{{t "orchestrator.filter-all"}}</button>
115
+ <button
116
+ type="button"
117
+ class="px-2 py-0.5 rounded text-xs transition-colors
118
+ {{if
119
+ (eq this.driverFilter 'online')
120
+ 'bg-indigo-500 text-white'
121
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
122
+ }}"
123
+ {{on "click" (fn this.setDriverFilter "online")}}
124
+ >{{t "orchestrator.filter-online"}}</button>
125
+ <button
126
+ type="button"
127
+ class="px-2 py-0.5 rounded text-xs transition-colors
128
+ {{if
129
+ (eq this.driverFilter 'offline')
130
+ 'bg-indigo-500 text-white'
131
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
132
+ }}"
133
+ {{on "click" (fn this.setDriverFilter "offline")}}
134
+ >{{t "orchestrator.filter-offline"}}</button>
135
+ <button
136
+ type="button"
137
+ class="px-2 py-0.5 rounded text-xs transition-colors
138
+ {{if
139
+ (eq this.driverFilter 'on-shift')
140
+ 'bg-indigo-500 text-white'
141
+ 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
142
+ }}"
143
+ {{on "click" (fn this.setDriverFilter "on-shift")}}
144
+ >{{t "orchestrator.filter-on-job"}}</button>
145
+ </div>
146
+ {{/if}}
147
+ </div>
148
+
149
+ {{! Tab content }}
150
+ <div class="flex-1 overflow-y-auto">
151
+ {{#if @isLoading}}
152
+ <div class="flex justify-center py-8"><Spinner /></div>
153
+
154
+ {{! ── Vehicles tab ── }}
155
+ {{else if (eq this.activeTab "vehicles")}}
156
+ {{#if this.filteredVehicles.length}}
157
+ {{#each this.filteredVehicles as |vehicle|}}
158
+ <button
159
+ type="button"
160
+ class="w-full text-left flex items-start gap-3 px-3 py-2.5 border-b dark:border-gray-700/60 cursor-pointer select-none transition-colors
161
+ {{if
162
+ (includes vehicle.public_id this.selectedVehicleIdsArray)
163
+ 'bg-indigo-100 dark:bg-indigo-900/60 hover:bg-indigo-100 dark:hover:bg-indigo-900/60'
164
+ 'hover:bg-gray-50 dark:hover:bg-gray-800/60'
165
+ }}"
166
+ {{on "click" (fn @onToggleVehicle vehicle)}}
167
+ >
168
+ {{! Avatar (photo_url) — driver online dot top-right }}
169
+ <div class="relative flex-shrink-0 mt-0.5">
170
+ <img
171
+ src={{vehicle.photo_url}}
172
+ alt={{or vehicle.display_name "Vehicle"}}
173
+ class="w-9 h-9 rounded object-cover bg-gray-200 dark:bg-gray-700"
174
+ {{fallback-img-src (config "defaultValues.vehicleAvatar")}}
175
+ />
176
+ <span
177
+ class="absolute top-0 left-0 block w-2.5 h-2.5 rounded-full border-2 border-white dark:border-gray-900
178
+ {{if vehicle.driver.online 'bg-green-500' 'bg-gray-400'}}"
179
+ ></span>
180
+ </div>
181
+ {{! All fields — always shown, "-" when absent }}
182
+ <div class="flex-1 min-w-0 space-y-0.5">
183
+ {{! Name + vehicle status badge }}
184
+ <div class="flex items-center justify-between gap-1.5">
185
+ <span class="text-xs font-semibold truncate dark:text-white">{{or vehicle.display_name "-"}}</span>
186
+ {{#if vehicle.status}}
187
+ <Badge @status={{vehicle.status}}>{{smart-humanize vehicle.status}}</Badge>
188
+ {{/if}}
189
+ </div>
190
+ {{! Assigned driver }}
191
+ <div class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
192
+ <FaIcon @icon="id-card-alt" @size="xs" class="w-3 flex-shrink-0" />
193
+ <span class="truncate">{{or vehicle.driver.name (t "orchestrator.no-driver-assigned")}}</span>
194
+ </div>
195
+ {{! Plate number }}
196
+ <div class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
197
+ <FaIcon @icon="hashtag" @size="xs" class="w-3 flex-shrink-0" />
198
+ <span class="truncate">{{or vehicle.plate_number "-"}}</span>
199
+ </div>
200
+ {{! Call sign }}
201
+ <div class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
202
+ <FaIcon @icon="broadcast-tower" @size="xs" class="w-3 flex-shrink-0" />
203
+ <span class="truncate">{{or vehicle.call_sign "-"}}</span>
204
+ </div>
205
+ </div>
206
+ </button>
207
+ {{/each}}
208
+ {{else}}
209
+ <div class="flex flex-col items-center justify-center py-12 text-gray-400 text-xs">
210
+ <FaIcon @icon="truck" @size="2x" class="mb-2 opacity-40" />
211
+ {{#if this.vehicleSearch}}
212
+ {{t "orchestrator.no-search-results"}}
213
+ {{else}}
214
+ {{t "orchestrator.no-available-vehicles"}}
215
+ {{/if}}
216
+ </div>
217
+ {{/if}}
218
+
219
+ {{! ── Drivers tab ── }}
220
+ {{else}}
221
+ {{#if this.filteredDrivers.length}}
222
+ {{#each this.filteredDrivers as |driver|}}
223
+ <button
224
+ type="button"
225
+ class="w-full text-left flex items-start gap-3 px-3 py-2.5 border-b dark:border-gray-700/60 cursor-pointer select-none transition-colors
226
+ {{if
227
+ (includes driver.public_id this.selectedDriverIdsArray)
228
+ 'bg-indigo-100 dark:bg-indigo-900/60 hover:bg-indigo-100 dark:hover:bg-indigo-900/60'
229
+ 'hover:bg-gray-50 dark:hover:bg-gray-800/60'
230
+ }}"
231
+ {{on "click" (fn @onToggleDriver driver)}}
232
+ >
233
+ {{! Avatar — online dot top-right }}
234
+ <div class="relative flex-shrink-0 mt-0.5">
235
+ <img
236
+ src={{driver.photo_url}}
237
+ alt={{or driver.name "Driver"}}
238
+ class="w-9 h-9 rounded-full object-cover bg-gray-200 dark:bg-gray-700"
239
+ {{fallback-img-src (config "defaultValues.driverImage")}}
240
+ />
241
+ <span
242
+ class="absolute top-0 left-0 block w-2.5 h-2.5 rounded-full border-2 border-white dark:border-gray-900 {{if driver.online 'bg-green-500' 'bg-gray-400'}}"
243
+ ></span>
244
+ </div>
245
+ {{! All fields — always shown, "-" when absent }}
246
+ <div class="flex-1 min-w-0 space-y-0.5">
247
+ <div class="text-xs font-semibold truncate dark:text-white">{{or driver.name "-"}}</div>
248
+ {{! Vehicle }}
249
+ <div class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
250
+ <FaIcon @icon="truck" @size="xs" class="w-3 flex-shrink-0" />
251
+ <span class="truncate">{{or driver.vehicle.display_name (t "orchestrator.no-vehicle-assigned")}}</span>
252
+ </div>
253
+ {{! Phone }}
254
+ <div class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
255
+ <FaIcon @icon="phone" @size="xs" class="w-3 flex-shrink-0" />
256
+ <span class="truncate">{{or driver.phone "-"}}</span>
257
+ </div>
258
+ {{! Email }}
259
+ <div class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
260
+ <FaIcon @icon="envelope" @size="xs" class="w-3 flex-shrink-0" />
261
+ <span class="truncate">{{or driver.email "-"}}</span>
262
+ </div>
263
+ </div>
264
+ </button>
265
+ {{/each}}
266
+ {{else}}
267
+ <div class="flex flex-col items-center justify-center py-12 text-gray-400 text-xs">
268
+ <FaIcon @icon="id-card-alt" @size="2x" class="mb-2 opacity-40" />
269
+ {{#if this.driverSearch}}
270
+ {{t "orchestrator.no-search-results"}}
271
+ {{else}}
272
+ {{t "orchestrator.no-available-drivers"}}
273
+ {{/if}}
274
+ </div>
275
+ {{/if}}
276
+ {{/if}}
277
+ </div>
278
+
279
+ {{! Selection info bar }}
280
+ {{#if (or this.selectedDriverIdsArray.length this.selectedVehicleIdsArray.length)}}
281
+ <div class="px-3 py-2 border-t dark:border-gray-700 bg-indigo-900/20 flex items-center justify-between flex-shrink-0">
282
+ <span class="text-xs text-indigo-400">
283
+ {{#if this.selectedVehicleIdsArray.length}}
284
+ {{this.selectedVehicleIdsArray.length}}
285
+ vehicles selected
286
+ {{/if}}
287
+ {{#if (and this.selectedVehicleIdsArray.length this.selectedDriverIdsArray.length)}}
288
+ ·
289
+ {{/if}}
290
+ {{#if this.selectedDriverIdsArray.length}}
291
+ {{this.selectedDriverIdsArray.length}}
292
+ {{t "orchestrator.drivers-selected"}}
293
+ {{/if}}
294
+ </span>
295
+ <button type="button" class="text-xs text-gray-400 hover:text-white" {{on "click" this.clearAllSelections}}>
296
+ {{t "orchestrator.clear-selection"}}
297
+ </button>
298
+ </div>
299
+ {{/if}}
300
+
301
+ </div>