@fleetbase/fleetops-engine 0.6.38 → 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 (429) 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.js +6 -0
  155. package/addon/controllers/operations/scheduler/fleet-schedule.js +341 -0
  156. package/addon/controllers/operations/scheduler/index.js +799 -275
  157. package/addon/controllers/operations/scheduler.js +21 -0
  158. package/addon/controllers/settings/orchestrator.js +70 -0
  159. package/addon/controllers/settings/scheduling.js +155 -0
  160. package/addon/extension.js +19 -0
  161. package/addon/instance-initializers/register-vroom-allocation.js +27 -0
  162. package/addon/models/maintenance-schedule.js +61 -0
  163. package/addon/routes/maintenance/equipment/index/details.js +27 -1
  164. package/addon/routes/maintenance/equipment/index/edit.js +27 -1
  165. package/addon/routes/maintenance/maintenances/index/details/index.js +3 -0
  166. package/addon/routes/maintenance/maintenances/index/details.js +29 -0
  167. package/addon/routes/maintenance/maintenances/index/edit.js +29 -0
  168. package/addon/routes/maintenance/maintenances/index/new.js +3 -0
  169. package/addon/routes/maintenance/maintenances/index.js +23 -0
  170. package/addon/routes/maintenance/maintenances.js +3 -0
  171. package/addon/routes/maintenance/parts/index/details.js +27 -1
  172. package/addon/routes/maintenance/parts/index/edit.js +27 -1
  173. package/addon/routes/maintenance/schedules/index/details/index.js +2 -0
  174. package/addon/routes/maintenance/schedules/index/details/work-orders.js +11 -0
  175. package/addon/routes/maintenance/schedules/index/details.js +25 -0
  176. package/addon/routes/maintenance/schedules/index/edit.js +25 -0
  177. package/addon/routes/maintenance/schedules/index/new.js +2 -0
  178. package/addon/routes/maintenance/schedules/index.js +21 -0
  179. package/addon/routes/maintenance/schedules.js +2 -0
  180. package/addon/routes/maintenance/work-orders/index/details.js +27 -1
  181. package/addon/routes/maintenance/work-orders/index/edit.js +27 -1
  182. package/addon/routes/management/vehicles/index/details/maintenance-history.js +3 -0
  183. package/addon/routes/management/vehicles/index/details/schedules.js +3 -0
  184. package/addon/routes/management/vehicles/index/details/work-orders.js +3 -0
  185. package/addon/routes/operations/orchestrator.js +23 -0
  186. package/addon/routes/operations/scheduler/fleet-schedule.js +28 -0
  187. package/addon/routes/operations/scheduler/index.js +48 -26
  188. package/addon/routes/operations/scheduler.js +14 -1
  189. package/addon/routes/settings/orchestrator.js +27 -0
  190. package/addon/routes/settings/scheduling.js +3 -0
  191. package/addon/routes.js +31 -1
  192. package/addon/services/driver-actions.js +40 -7
  193. package/addon/services/driver-scheduling.js +4 -1
  194. package/addon/services/equipment-actions.js +15 -5
  195. package/addon/services/leaflet-map-manager.js +14 -6
  196. package/addon/services/maintenance-actions.js +17 -14
  197. package/addon/services/maintenance-schedule-actions.js +118 -0
  198. package/addon/services/orchestration-engine-interface.js +49 -0
  199. package/addon/services/orchestration-engine.js +74 -0
  200. package/addon/services/order-actions.js +15 -0
  201. package/addon/services/order-allocation.js +116 -0
  202. package/addon/services/part-actions.js +12 -2
  203. package/addon/services/scheduling.js +316 -0
  204. package/addon/services/vehicle-actions.js +70 -7
  205. package/addon/services/vroom-allocation-engine.js +45 -0
  206. package/addon/services/work-order-actions.js +80 -0
  207. package/addon/styles/fleetops-engine.css +1658 -0
  208. package/addon/templates/analytics/reports/index/edit.hbs +1 -1
  209. package/addon/templates/analytics/reports/index/new.hbs +1 -1
  210. package/addon/templates/application.hbs +6 -1
  211. package/addon/templates/connectivity/devices/index/details/events.hbs +0 -1
  212. package/addon/templates/connectivity/devices.hbs +0 -1
  213. package/addon/templates/connectivity/events/index/details.hbs +0 -1
  214. package/addon/templates/connectivity/events.hbs +0 -1
  215. package/addon/templates/connectivity/sensors.hbs +0 -1
  216. package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
  217. package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
  218. package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
  219. package/addon/templates/connectivity/telematics.hbs +0 -1
  220. package/addon/templates/connectivity/tracking.hbs +0 -1
  221. package/addon/templates/connectivity.hbs +0 -1
  222. package/addon/templates/maintenance/equipment/index/details/index.hbs +1 -2
  223. package/addon/templates/maintenance/equipment/index/details.hbs +15 -2
  224. package/addon/templates/maintenance/equipment/index/edit.hbs +12 -2
  225. package/addon/templates/maintenance/equipment/index/new.hbs +1 -2
  226. package/addon/templates/maintenance/equipment/index.hbs +48 -13
  227. package/addon/templates/maintenance/equipment.hbs +0 -1
  228. package/addon/templates/maintenance/maintenances/index/details/index.hbs +1 -0
  229. package/addon/templates/maintenance/maintenances/index/details.hbs +15 -0
  230. package/addon/templates/maintenance/maintenances/index/edit.hbs +12 -0
  231. package/addon/templates/maintenance/maintenances/index/new.hbs +11 -0
  232. package/addon/templates/maintenance/maintenances/index.hbs +14 -0
  233. package/addon/templates/maintenance/maintenances.hbs +1 -0
  234. package/addon/templates/maintenance/parts/index/details/index.hbs +1 -2
  235. package/addon/templates/maintenance/parts/index/details.hbs +15 -2
  236. package/addon/templates/maintenance/parts/index/edit.hbs +12 -2
  237. package/addon/templates/maintenance/parts/index/new.hbs +1 -2
  238. package/addon/templates/maintenance/parts/index.hbs +48 -13
  239. package/addon/templates/maintenance/parts.hbs +0 -1
  240. package/addon/templates/maintenance/schedules/index/details/index.hbs +1 -0
  241. package/addon/templates/maintenance/schedules/index/details/work-orders.hbs +39 -0
  242. package/addon/templates/maintenance/schedules/index/details.hbs +14 -0
  243. package/addon/templates/maintenance/schedules/index/edit.hbs +12 -0
  244. package/addon/templates/maintenance/schedules/index/new.hbs +11 -0
  245. package/addon/templates/maintenance/schedules/index.hbs +40 -0
  246. package/addon/templates/maintenance/schedules.hbs +1 -0
  247. package/addon/templates/maintenance/work-orders/index/details.hbs +2 -1
  248. package/addon/templates/maintenance/work-orders/index/edit.hbs +2 -4
  249. package/addon/templates/maintenance/work-orders/index/new.hbs +1 -2
  250. package/addon/templates/maintenance/work-orders.hbs +0 -1
  251. package/addon/templates/management/contacts/customers/edit.hbs +1 -2
  252. package/addon/templates/management/contacts/customers/new.hbs +1 -2
  253. package/addon/templates/management/contacts/customers.hbs +1 -1
  254. package/addon/templates/management/contacts/index/edit.hbs +1 -2
  255. package/addon/templates/management/contacts/index/new.hbs +1 -2
  256. package/addon/templates/management/drivers/index/details/orders.hbs +0 -1
  257. package/addon/templates/management/drivers/index/edit.hbs +1 -2
  258. package/addon/templates/management/drivers/index/new.hbs +1 -2
  259. package/addon/templates/management/fleets/index/edit.hbs +1 -2
  260. package/addon/templates/management/fleets/index/new.hbs +1 -2
  261. package/addon/templates/management/fleets/index.hbs +1 -2
  262. package/addon/templates/management/fuel-reports/index/edit.hbs +1 -2
  263. package/addon/templates/management/fuel-reports/index/new.hbs +1 -2
  264. package/addon/templates/management/fuel-reports/index.hbs +1 -2
  265. package/addon/templates/management/issues/index/edit.hbs +1 -2
  266. package/addon/templates/management/issues/index/new.hbs +1 -2
  267. package/addon/templates/management/issues/index.hbs +1 -2
  268. package/addon/templates/management/places/index/details/activity.hbs +0 -1
  269. package/addon/templates/management/places/index/details/comments.hbs +0 -1
  270. package/addon/templates/management/places/index/details/documents.hbs +0 -1
  271. package/addon/templates/management/places/index/details/map.hbs +0 -1
  272. package/addon/templates/management/places/index/details/operations.hbs +0 -1
  273. package/addon/templates/management/places/index/details/performance.hbs +0 -1
  274. package/addon/templates/management/places/index/details/rules.hbs +0 -1
  275. package/addon/templates/management/vehicles/index/details/equipment.hbs +0 -1
  276. package/addon/templates/management/vehicles/index/details/maintenance-history.hbs +2 -0
  277. package/addon/templates/management/vehicles/index/details/schedules.hbs +2 -0
  278. package/addon/templates/management/vehicles/index/details/work-orders.hbs +2 -0
  279. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  280. package/addon/templates/management/vehicles/index/edit.hbs +1 -2
  281. package/addon/templates/management/vehicles/index/new.hbs +1 -2
  282. package/addon/templates/management/vendors/index/edit.hbs +1 -2
  283. package/addon/templates/management/vendors/index/new.hbs +1 -2
  284. package/addon/templates/management/vendors/index.hbs +1 -2
  285. package/addon/templates/management/vendors/integrated.hbs +1 -2
  286. package/addon/templates/operations/orchestrator.hbs +1 -0
  287. package/addon/templates/operations/orders/index.hbs +6 -1
  288. package/addon/templates/operations/scheduler/fleet-schedule.hbs +41 -0
  289. package/addon/templates/operations/scheduler/index.hbs +147 -88
  290. package/addon/templates/operations/scheduler.hbs +7 -1
  291. package/addon/templates/settings/avatars.hbs +1 -1
  292. package/addon/templates/settings/orchestrator.hbs +65 -0
  293. package/addon/templates/settings/payments/index.hbs +1 -5
  294. package/addon/templates/settings/scheduling.hbs +82 -0
  295. package/addon/utils/create-full-calendar-event-from-order.js +52 -14
  296. package/addon/utils/create-full-calendar-event-from-schedule-item.js +50 -0
  297. package/addon/utils/fleet-ops-options.js +254 -0
  298. package/addon/utils/route-colors.js +99 -0
  299. package/addon/utils/to-calendar-date.js +70 -0
  300. package/app/components/driver/schedule.js +1 -0
  301. package/app/components/maintenance/cost-panel.js +1 -0
  302. package/app/components/maintenance/panel-header.js +1 -0
  303. package/app/components/maintenance-schedule/details.js +1 -0
  304. package/app/components/maintenance-schedule/form.js +1 -0
  305. package/app/components/modals/add-driver-shift.js +1 -0
  306. package/app/components/modals/bulk-assign-orders.js +1 -0
  307. package/app/components/modals/driver-shift.js +1 -0
  308. package/app/components/modals/orchestrator-import.js +1 -0
  309. package/app/components/modals/scheduling-conflict.js +1 -0
  310. package/app/components/modals/send-work-order.js +1 -0
  311. package/app/components/modals/set-driver-availability.js +1 -0
  312. package/app/components/orchestrator/card-fields-settings.js +1 -0
  313. package/app/components/orchestrator/order-pool.js +1 -0
  314. package/app/components/orchestrator/phase-builder.js +1 -0
  315. package/app/components/orchestrator/plan-viewer.js +1 -0
  316. package/app/components/orchestrator/resource-panel.js +1 -0
  317. package/app/components/orchestrator-workbench.js +1 -0
  318. package/app/components/vehicle/details/maintenance-history.js +1 -0
  319. package/app/components/vehicle/details/schedules.js +1 -0
  320. package/app/components/vehicle/details/work-orders.js +1 -0
  321. package/app/controllers/operations/orchestrator.js +1 -0
  322. package/app/controllers/settings/orchestrator.js +1 -0
  323. package/app/controllers/settings/scheduling.js +1 -0
  324. package/app/routes/operations/orchestrator.js +1 -0
  325. package/app/routes/settings/orchestrator.js +1 -0
  326. package/app/routes/settings/scheduling.js +1 -0
  327. package/app/services/maintenance-schedule-actions.js +1 -0
  328. package/app/services/orchestration-engine-interface.js +1 -0
  329. package/app/services/orchestration-engine.js +1 -0
  330. package/app/services/order-allocation.js +1 -0
  331. package/app/services/scheduling.js +1 -0
  332. package/app/services/vroom-allocation-engine.js +1 -0
  333. package/app/templates/settings/scheduling.js +1 -0
  334. package/app/utils/create-full-calendar-event-from-schedule-item.js +1 -0
  335. package/app/utils/route-colors.js +1 -0
  336. package/composer.json +5 -3
  337. package/extension.json +1 -1
  338. package/package.json +6 -5
  339. package/server/config/fleetops.php +20 -1
  340. package/server/migrations/2025_08_28_054927_create_parts_table.php +2 -2
  341. package/server/migrations/2025_08_28_054932_add_public_id_to_maintenance_tables.php +45 -0
  342. package/server/migrations/2025_09_01_000001_create_maintenance_schedules_table.php +88 -0
  343. package/server/migrations/2026_04_01_000001_fix_monetary_columns_in_parts_table.php +48 -0
  344. package/server/migrations/2026_04_01_000003_add_photo_uuid_to_equipment_and_parts_tables.php +61 -0
  345. package/server/migrations/2026_04_01_000004_add_public_id_to_equipments_table.php +38 -0
  346. package/server/migrations/2026_04_01_000005_add_missing_columns_to_parts_table.php +67 -0
  347. package/server/migrations/2026_04_04_000001_add_reminder_offsets_to_maintenance_schedules.php +44 -0
  348. package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +58 -0
  349. package/server/migrations/2026_04_08_000002_add_orchestrator_columns_to_drivers_table.php +41 -0
  350. package/server/migrations/2026_04_08_000003_add_orchestrator_columns_to_orders_table.php +38 -0
  351. package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +41 -0
  352. package/server/migrations/2026_04_08_000005_add_orchestrator_columns_to_waypoints_table.php +38 -0
  353. package/server/migrations/2026_04_09_000001_create_manifests_table.php +48 -0
  354. package/server/migrations/2026_04_09_000002_create_manifest_stops_table.php +48 -0
  355. package/server/migrations/2026_04_09_000003_add_manifest_uuid_to_orders_table.php +28 -0
  356. package/server/migrations/2026_04_13_000001_add_pod_notes_columns_to_waypoints_table.php +39 -0
  357. package/server/resources/views/mail/maintenance-schedule-reminder.blade.php +59 -0
  358. package/server/resources/views/mail/work-order-dispatched.blade.php +67 -0
  359. package/server/src/Auth/Schemas/FleetOps.php +44 -0
  360. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +150 -0
  361. package/server/src/Console/Commands/SendMaintenanceReminders.php +128 -0
  362. package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -0
  363. package/server/src/Http/Controllers/Internal/v1/EquipmentController.php +27 -0
  364. package/server/src/Http/Controllers/Internal/v1/LiveController.php +9 -2
  365. package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +165 -0
  366. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +304 -0
  367. package/server/src/Http/Controllers/Internal/v1/ManifestController.php +138 -0
  368. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +975 -0
  369. package/server/src/Http/Controllers/Internal/v1/OrderController.php +42 -0
  370. package/server/src/Http/Controllers/Internal/v1/PartController.php +27 -0
  371. package/server/src/Http/Controllers/Internal/v1/SettingController.php +118 -0
  372. package/server/src/Http/Controllers/Internal/v1/Traits/DriverSchedulingTrait.php +214 -0
  373. package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +68 -0
  374. package/server/src/Http/Resources/v1/Driver.php +1 -0
  375. package/server/src/Http/Resources/v1/Maintenance.php +138 -0
  376. package/server/src/Http/Resources/v1/MaintenanceSchedule.php +137 -0
  377. package/server/src/Http/Resources/v1/Orchestrator/Order.php +116 -0
  378. package/server/src/Http/Resources/v1/Order.php +7 -4
  379. package/server/src/Http/Resources/v1/Waypoint.php +7 -0
  380. package/server/src/Http/Resources/v1/WorkOrder.php +136 -0
  381. package/server/src/Imports/EquipmentImport.php +32 -0
  382. package/server/src/Imports/MaintenanceImport.php +32 -0
  383. package/server/src/Imports/MaintenanceScheduleImport.php +32 -0
  384. package/server/src/Imports/PartImport.php +32 -0
  385. package/server/src/Imports/WorkOrderImport.php +32 -0
  386. package/server/src/Jobs/ProcessAllocationJob.php +119 -0
  387. package/server/src/Listeners/HandleDeliveryCompletion.php +47 -0
  388. package/server/src/Listeners/NotifyDriverOnShiftChange.php +63 -0
  389. package/server/src/Mail/MaintenanceScheduleReminder.php +68 -0
  390. package/server/src/Mail/WorkOrderDispatched.php +58 -0
  391. package/server/src/Models/Asset.php +2 -2
  392. package/server/src/Models/Device.php +1 -1
  393. package/server/src/Models/Driver.php +82 -4
  394. package/server/src/Models/Equipment.php +62 -2
  395. package/server/src/Models/Maintenance.php +127 -9
  396. package/server/src/Models/MaintenanceSchedule.php +353 -0
  397. package/server/src/Models/Manifest.php +214 -0
  398. package/server/src/Models/ManifestStop.php +162 -0
  399. package/server/src/Models/Order.php +70 -0
  400. package/server/src/Models/OrderConfig.php +5 -2
  401. package/server/src/Models/Part.php +69 -3
  402. package/server/src/Models/Payload.php +7 -2
  403. package/server/src/Models/Place.php +1 -1
  404. package/server/src/Models/Sensor.php +1 -1
  405. package/server/src/Models/ServiceQuote.php +1 -1
  406. package/server/src/Models/Vehicle.php +20 -1
  407. package/server/src/Models/Warranty.php +1 -1
  408. package/server/src/Models/Waypoint.php +7 -1
  409. package/server/src/Models/WorkOrder.php +122 -12
  410. package/server/src/Notifications/DriverShiftChanged.php +110 -0
  411. package/server/src/Observers/WorkOrderObserver.php +107 -0
  412. package/server/src/Orchestration/Contracts/OrchestrationEngineInterface.php +63 -0
  413. package/server/src/Orchestration/Engines/DriverAssignmentEngine.php +265 -0
  414. package/server/src/Orchestration/Engines/GreedyOrchestrationEngine.php +155 -0
  415. package/server/src/Orchestration/Engines/RouteSequencingEngine.php +272 -0
  416. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +192 -0
  417. package/server/src/Orchestration/OrchestrationEngineRegistry.php +83 -0
  418. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +290 -0
  419. package/server/src/Providers/EventServiceProvider.php +7 -1
  420. package/server/src/Providers/FleetOpsServiceProvider.php +42 -15
  421. package/server/src/routes.php +65 -4
  422. package/translations/ar-ae.yml +44 -12
  423. package/translations/bg-bg.yaml +51 -10
  424. package/translations/en-us.yaml +444 -1
  425. package/translations/fr-fr.yaml +51 -10
  426. package/translations/mn-mn.yaml +51 -10
  427. package/translations/pt-br.yaml +51 -10
  428. package/translations/ru-ru.yaml +51 -10
  429. package/translations/vi-vn.yaml +48 -12
@@ -1,18 +1,143 @@
1
1
  import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { action } from '@ember/object';
4
+
5
+ /**
6
+ * Maps a polymorphic type value to the Ember Data model name used by ModelSelect.
7
+ */
8
+ const TYPE_TO_MODEL = {
9
+ 'fleet-ops:vehicle': 'vehicle',
10
+ 'fleet-ops:driver': 'driver',
11
+ 'fleet-ops:equipment': 'equipment',
12
+ 'fleet-ops:vendor': 'vendor',
13
+ 'Auth:User': 'user',
14
+ };
15
+
16
+ /**
17
+ * Maps a concrete Ember Data model name back to the backend polymorphic type string.
18
+ * Used to restore the type selector when editing an existing record that has a
19
+ * loaded @belongsTo('maintenance-subject', {polymorphic: true}) relationship.
20
+ */
21
+ const MAINTAINABLE_MODEL_TO_TYPE = {
22
+ 'maintenance-subject-vehicle': 'fleet-ops:vehicle',
23
+ 'maintenance-subject-equipment': 'fleet-ops:equipment',
24
+ // Fall-through for raw models passed from vehicle-actions.js
25
+ vehicle: 'fleet-ops:vehicle',
26
+ equipment: 'fleet-ops:equipment',
27
+ };
28
+
29
+ /**
30
+ * Maps a concrete Ember Data model name back to the backend polymorphic type string
31
+ * for the performed_by relationship.
32
+ */
33
+ const PERFORMED_BY_MODEL_TO_TYPE = {
34
+ 'facilitator-vendor': 'fleet-ops:vendor',
35
+ 'facilitator-contact': 'fleet-ops:contact',
36
+ 'facilitator-integrated-vendor': 'fleet-ops:vendor',
37
+ vendor: 'fleet-ops:vendor',
38
+ driver: 'fleet-ops:driver',
39
+ user: 'Auth:User',
40
+ };
2
41
 
3
42
  export default class MaintenanceFormComponent extends Component {
43
+ /** Maintenance type options — the category of maintenance activity. */
44
+ maintenanceTypeOptions = ['preventive', 'corrective', 'predictive', 'routine', 'emergency', 'inspection', 'repair', 'replacement', 'calibration'];
45
+
46
+ /** Status options for a maintenance record. */
47
+ statusOptions = ['scheduled', 'in_progress', 'completed', 'cancelled', 'on_hold'];
48
+
49
+ /** Priority options for a maintenance record. */
50
+ priorityOptions = ['low', 'medium', 'high', 'critical'];
51
+
4
52
  /**
5
- * Maintenance type options
53
+ * Polymorphic maintainable type options — the asset being maintained.
54
+ * Uses label/value objects so the PowerSelect trigger shows a human-readable
55
+ * label instead of the raw model type string.
6
56
  */
7
- maintenanceTypeOptions = ['preventive', 'corrective', 'predictive', 'routine', 'emergency', 'inspection', 'repair', 'replacement', 'calibration'];
57
+ maintainableTypeOptions = [
58
+ { value: 'fleet-ops:vehicle', label: 'Vehicle' },
59
+ { value: 'fleet-ops:equipment', label: 'Equipment' },
60
+ ];
8
61
 
9
62
  /**
10
- * Status options for maintenance
63
+ * Polymorphic performed-by type options who carried out the maintenance.
64
+ * Uses label/value objects so the PowerSelect trigger shows a human-readable
65
+ * label instead of the raw model type string.
11
66
  */
12
- statusOptions = ['scheduled', 'in_progress', 'completed', 'cancelled', 'on_hold'];
67
+ performedByTypeOptions = [
68
+ { value: 'fleet-ops:vendor', label: 'Vendor' },
69
+ { value: 'fleet-ops:driver', label: 'Driver' },
70
+ { value: 'Auth:User', label: 'User' },
71
+ ];
72
+
73
+ /** The currently selected maintainable type option object. */
74
+ @tracked selectedMaintainableType = null;
75
+
76
+ /** The currently selected performed-by type option object. */
77
+ @tracked selectedPerformedByType = null;
78
+
79
+ /** Derived Ember Data model name for the currently selected maintainable type. */
80
+ @tracked maintainableModelName = null;
81
+
82
+ /** Derived Ember Data model name for the currently selected performed-by type. */
83
+ @tracked performedByModelName = null;
84
+
85
+ constructor(owner, args) {
86
+ super(owner, args);
87
+ const { resource } = args;
88
+ // Restore maintainable type selection from the polymorphic relationship.
89
+ // When editing an existing record, resource.maintainable is a loaded MaintenanceSubject model.
90
+ // When creating from vehicle-actions.js, resource.maintainable may be a raw vehicle/equipment model.
91
+ const maintainable = resource?.maintainable;
92
+ if (maintainable) {
93
+ const modelName = maintainable.constructor?.modelName ?? maintainable.modelName;
94
+ const typeValue = MAINTAINABLE_MODEL_TO_TYPE[modelName] ?? null;
95
+ if (typeValue) {
96
+ this.selectedMaintainableType = this.maintainableTypeOptions.find((o) => o.value === typeValue) ?? null;
97
+ this.maintainableModelName = TYPE_TO_MODEL[typeValue] ?? null;
98
+ }
99
+ }
100
+ // Restore performed_by type selection from the polymorphic relationship.
101
+ const performedBy = resource?.performed_by;
102
+ if (performedBy) {
103
+ const modelName = performedBy.constructor?.modelName ?? performedBy.modelName;
104
+ const typeValue = PERFORMED_BY_MODEL_TO_TYPE[modelName] ?? null;
105
+ if (typeValue) {
106
+ this.selectedPerformedByType = this.performedByTypeOptions.find((o) => o.value === typeValue) ?? null;
107
+ this.performedByModelName = TYPE_TO_MODEL[typeValue] ?? null;
108
+ }
109
+ }
110
+ }
13
111
 
14
112
  /**
15
- * Priority options
113
+ * Handles a change to the maintainable type selector. Resets the
114
+ * maintainable relationship so a stale association is not persisted.
16
115
  */
17
- priorityOptions = ['low', 'medium', 'high', 'critical'];
116
+ @action onMaintainableTypeChange(option) {
117
+ this.selectedMaintainableType = option;
118
+ // Clear the maintainable relationship — user must re-select the asset
119
+ this.args.resource.maintainable = null;
120
+ this.maintainableModelName = TYPE_TO_MODEL[option?.value] ?? null;
121
+ }
122
+
123
+ /** Assigns the selected maintainable model to the resource. */
124
+ @action assignMaintainable(model) {
125
+ this.args.resource.maintainable = model;
126
+ }
127
+
128
+ /**
129
+ * Handles a change to the performed-by type selector. Resets the
130
+ * performed-by relationship so a stale association is not persisted.
131
+ */
132
+ @action onPerformedByTypeChange(option) {
133
+ this.selectedPerformedByType = option;
134
+ // Clear the performed_by relationship — user must re-select
135
+ this.args.resource.performed_by = null;
136
+ this.performedByModelName = TYPE_TO_MODEL[option?.value] ?? null;
137
+ }
138
+
139
+ /** Assigns the selected performer model to the resource. */
140
+ @action assignPerformedBy(model) {
141
+ this.args.resource.performed_by = model;
142
+ }
18
143
  }
@@ -0,0 +1,31 @@
1
+ <div class="px-4 py-2">
2
+ <div class="flex flex-1 flex-row items-start justify-between">
3
+ <div class="flex flex-col">
4
+ <div class="flex flex-row items-center space-x-2">
5
+ <h1 class="text-gray-900 dark:text-white font-semibold text-lg">{{n-a @resource.summary}}</h1>
6
+ <Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge>
7
+ </div>
8
+ <div class="flex flex-row space-x-3 mt-1">
9
+ <div class="text-xs text-gray-400 dark:text-gray-500">{{smart-humanize @resource.type}}</div>
10
+ {{#if @resource.priority}}
11
+ <div class="text-xs text-gray-400 dark:text-gray-500">{{t "column.priority"}}: {{smart-humanize @resource.priority}}</div>
12
+ {{/if}}
13
+ {{#if @resource.scheduled_at}}
14
+ <div class="text-xs text-gray-400 dark:text-gray-500">{{t "column.scheduled-at"}}: {{format-date @resource.scheduled_at}}</div>
15
+ {{/if}}
16
+ </div>
17
+ </div>
18
+ <div class="next-view-header-right">
19
+ <Layout::Resource::Panel::HeaderActions
20
+ @resource={{@resource}}
21
+ @saveTask={{@saveTask}}
22
+ @saveOptions={{@saveOptions}}
23
+ @saveDisabled={{@saveDisabled}}
24
+ @pojoResource={{@pojoResource}}
25
+ @authSchema={{@authSchema}}
26
+ @actionButtons={{@actionButtons}}
27
+ @onPressCancel={{@onPressCancel}}
28
+ />
29
+ </div>
30
+ </div>
31
+ </div>
@@ -0,0 +1,2 @@
1
+ import Component from '@glimmer/component';
2
+ export default class MaintenancePanelHeaderComponent extends Component {}
@@ -0,0 +1,260 @@
1
+ <div class="next-content-panel-wrapper" ...attributes>
2
+
3
+ {{! ===== OVERVIEW ===== }}
4
+ <ContentPanel @title="Overview" @open={{true}} @wrapperClass="bordered-top">
5
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
6
+
7
+ {{! — Identity — }}
8
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
9
+ Identity
10
+ </div>
11
+ <div class="field-info-container">
12
+ <div class="field-name">ID</div>
13
+ <ClickToCopy @value={{@resource.public_id}} class="field-value font-mono">{{n-a @resource.public_id}}</ClickToCopy>
14
+ </div>
15
+ <div class="field-info-container">
16
+ <div class="field-name">Name</div>
17
+ <div class="field-value">{{n-a @resource.name}}</div>
18
+ </div>
19
+ <div class="field-info-container">
20
+ <div class="field-name">Type</div>
21
+ <div class="field-value">{{n-a (smart-humanize @resource.type)}}</div>
22
+ </div>
23
+ <div class="field-info-container">
24
+ <div class="field-name">Status</div>
25
+ <div class="field-value">
26
+ <Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge>
27
+ </div>
28
+ </div>
29
+
30
+ {{! — Assignment — }}
31
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
32
+ Assignment
33
+ </div>
34
+ <div class="field-info-container">
35
+ <div class="field-name">Asset</div>
36
+ <div class="field-value">{{n-a (or @resource.subject.displayName @resource.subject.name @resource.subject_name)}}</div>
37
+ </div>
38
+ {{#if (or @resource.default_assignee.displayName @resource.default_assignee.name)}}
39
+ <div class="field-info-container">
40
+ <div class="field-name">Default Assignee</div>
41
+ <div class="field-value">{{n-a (or @resource.default_assignee.displayName @resource.default_assignee.name)}}</div>
42
+ </div>
43
+ {{/if}}
44
+ <div class="field-info-container">
45
+ <div class="field-name">Default Priority</div>
46
+ <div class="field-value">
47
+ {{#if (eq @resource.default_priority "critical")}}
48
+ <Badge @status="danger">Critical</Badge>
49
+ {{else if (eq @resource.default_priority "high")}}
50
+ <Badge @status="warning">High</Badge>
51
+ {{else if (eq @resource.default_priority "medium")}}
52
+ <Badge @status="info">Medium</Badge>
53
+ {{else}}
54
+ <Badge @status="default">Low</Badge>
55
+ {{/if}}
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </ContentPanel>
60
+
61
+ {{! ===== INTERVAL TRIGGERS ===== }}
62
+ <ContentPanel @title="Interval Triggers" @open={{true}} @wrapperClass="bordered-top">
63
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
64
+
65
+ {{#if @resource.interval_value}}
66
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
67
+ Time-Based
68
+ </div>
69
+ <div class="field-info-container">
70
+ <div class="field-name">Repeat Every</div>
71
+ <div class="field-value">{{@resource.interval_value}} {{@resource.interval_unit}}</div>
72
+ </div>
73
+ {{/if}}
74
+
75
+ {{#if @resource.interval_distance}}
76
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
77
+ Distance-Based
78
+ </div>
79
+ <div class="field-info-container">
80
+ <div class="field-name">Every (km)</div>
81
+ <div class="field-value">{{@resource.interval_distance}} km</div>
82
+ </div>
83
+ {{/if}}
84
+
85
+ {{#if @resource.interval_engine_hours}}
86
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
87
+ Engine Hours-Based
88
+ </div>
89
+ <div class="field-info-container">
90
+ <div class="field-name">Every (hours)</div>
91
+ <div class="field-value">{{@resource.interval_engine_hours}} hrs</div>
92
+ </div>
93
+ {{/if}}
94
+ </div>
95
+ </ContentPanel>
96
+
97
+ {{! ===== SERVICE HISTORY & NEXT DUE ===== }}
98
+ <ContentPanel @title="Service History & Next Due" @open={{true}} @wrapperClass="bordered-top">
99
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
100
+
101
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
102
+ Last Service
103
+ </div>
104
+ <div class="field-info-container">
105
+ <div class="field-name">Last Service Date</div>
106
+ <div class="field-value">{{n-a (format-date-fns @resource.last_service_date "dd MMM yyyy")}}</div>
107
+ </div>
108
+ <div class="field-info-container">
109
+ <div class="field-name">Last Odometer</div>
110
+ <div class="field-value">
111
+ {{#if @resource.last_service_odometer}}
112
+ {{@resource.last_service_odometer}}
113
+ km
114
+ {{else}}
115
+
116
+ {{/if}}
117
+ </div>
118
+ </div>
119
+ <div class="field-info-container">
120
+ <div class="field-name">Last Engine Hours</div>
121
+ <div class="field-value">{{n-a @resource.last_service_engine_hours}}</div>
122
+ </div>
123
+
124
+ <div class="col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
125
+ Next Due
126
+ </div>
127
+ <div class="field-info-container">
128
+ <div class="field-name">Next Due Date</div>
129
+ <div class="field-value">{{n-a (format-date-fns @resource.next_due_date "dd MMM yyyy")}}</div>
130
+ </div>
131
+ <div class="field-info-container">
132
+ <div class="field-name">Next Due Odometer</div>
133
+ <div class="field-value">
134
+ {{#if @resource.next_due_odometer}}
135
+ {{@resource.next_due_odometer}}
136
+ km
137
+ {{else}}
138
+
139
+ {{/if}}
140
+ </div>
141
+ </div>
142
+ <div class="field-info-container">
143
+ <div class="field-name">Next Due Engine Hours</div>
144
+ <div class="field-value">{{n-a @resource.next_due_engine_hours}}</div>
145
+ </div>
146
+ </div>
147
+ </ContentPanel>
148
+
149
+ {{! ===== INSTRUCTIONS ===== }}
150
+ {{#if @resource.instructions}}
151
+ <ContentPanel @title="Instructions" @open={{false}} @wrapperClass="bordered-top">
152
+ <div class="text-xs text-gray-700 dark:text-gray-300 whitespace-pre-wrap">
153
+ {{@resource.instructions}}
154
+ </div>
155
+ </ContentPanel>
156
+ {{/if}}
157
+
158
+ {{! ===== UPCOMING SCHEDULE CALENDAR (time-based only) ===== }}
159
+ {{#if this.isTimeBased}}
160
+ <ContentPanel @title="Upcoming Schedule" @open={{true}} @wrapperClass="bordered-top" @panelBodyWrapperClass="px-0i">
161
+ {{! Calendar header }}
162
+ <div class="flex items-center justify-between px-6 pb-2">
163
+ <button type="button" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400" {{on "click" this.prevMonth}}>
164
+ <FaIcon @icon="chevron-left" @size="xs" />
165
+ </button>
166
+ <span class="text-xs font-semibold text-gray-700 dark:text-gray-200">{{this.calendarMonthName}}</span>
167
+ <button type="button" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400" {{on "click" this.nextMonth}}>
168
+ <FaIcon @icon="chevron-right" @size="xs" />
169
+ </button>
170
+ </div>
171
+
172
+ {{! Day-of-week header row }}
173
+ <div class="grid grid-cols-7 text-center pb-1">
174
+ {{#each this.dayNames as |dayName|}}
175
+ <div class="text-xs font-medium text-gray-400 dark:text-gray-500 py-1">{{dayName}}</div>
176
+ {{/each}}
177
+ </div>
178
+
179
+ {{! Calendar grid }}
180
+ <div class="pb-4">
181
+ {{#each this.calendarWeeks as |week|}}
182
+ <div class="grid grid-cols-7 text-center">
183
+ {{#each week as |cell|}}
184
+ <div class="py-1">
185
+ <span
186
+ class="inline-flex items-center justify-center w-6 h-6 rounded-full text-xs
187
+ {{if cell.isScheduled 'bg-blue-500 text-white font-semibold'}}
188
+ {{if cell.isToday (if cell.isScheduled 'ring-2 ring-offset-1 ring-blue-400' 'ring-2 ring-blue-400 text-blue-600 dark:text-blue-400 font-semibold')}}
189
+ {{unless cell.isCurrentMonth 'text-gray-300 dark:text-gray-600'}}
190
+ "
191
+ >
192
+ {{cell.day}}
193
+ </span>
194
+ </div>
195
+ {{/each}}
196
+ </div>
197
+ {{/each}}
198
+ </div>
199
+
200
+ {{! Legend }}
201
+ <div class="flex items-center gap-4 px-3 py-2 border-t border-gray-100 dark:border-gray-700">
202
+ <div class="flex items-center gap-1.5">
203
+ <span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-500 text-white text-xs">1</span>
204
+ <span class="text-xs text-gray-500 dark:text-gray-400">Scheduled service</span>
205
+ </div>
206
+ <div class="flex items-center gap-1.5">
207
+ <span class="inline-flex items-center justify-center w-5 h-5 rounded-full ring-2 ring-blue-400 text-xs text-blue-600">1</span>
208
+ <span class="text-xs text-gray-500 dark:text-gray-400">Today</span>
209
+ </div>
210
+ </div>
211
+
212
+ {{! Upcoming occurrences list }}
213
+ {{#if this.upcomingOccurrences.length}}
214
+ <div class="px-3 pb-3 pt-1 border-t border-gray-100 dark:border-gray-700">
215
+ <div class="py-2">
216
+ <p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">Next occurrences</p>
217
+ </div>
218
+ <div class="space-y-1 pl-4">
219
+ {{#each this.upcomingOccurrences as |occurrence index|}}
220
+ <div class="flex items-center gap-2">
221
+ <span
222
+ class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 text-xs font-semibold flex-shrink-0"
223
+ >
224
+ {{add index 1}}
225
+ </span>
226
+ <span class="text-xs text-gray-700 dark:text-gray-200">
227
+ {{format-date-fns occurrence "EEEE, MMMM d, yyyy"}}
228
+ </span>
229
+ </div>
230
+ {{/each}}
231
+ </div>
232
+ </div>
233
+ {{/if}}
234
+ </ContentPanel>
235
+ {{/if}}
236
+
237
+ {{! ===== REMINDER OFFSETS ===== }}
238
+ {{#if @resource.reminder_offsets.length}}
239
+ <ContentPanel @title="Reminder Emails" @open={{true}} @wrapperClass="bordered-top">
240
+ <div class="text-xs text-gray-700 dark:text-gray-300">
241
+ <p class="mb-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold">
242
+ The default assignee will receive email reminders at:
243
+ </p>
244
+ <div class="flex flex-wrap gap-1">
245
+ {{#each @resource.reminder_offsets as |offset|}}
246
+ <span class="inline-flex items-center bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs px-2 py-0.5 rounded">
247
+ {{offset}}
248
+ days before
249
+ </span>
250
+ {{/each}}
251
+ </div>
252
+ </div>
253
+ </ContentPanel>
254
+ {{/if}}
255
+
256
+ {{! ===== CUSTOM FIELDS ===== }}
257
+ <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
258
+
259
+ <Spacer @height="200px" />
260
+ </div>
@@ -0,0 +1,158 @@
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
+
6
+ /**
7
+ * Compute the next N occurrences of a time-based maintenance schedule
8
+ * starting from the given `nextDueDate`, stepping by `intervalValue` `intervalUnit`.
9
+ */
10
+ function computeOccurrences(nextDueDate, intervalValue, intervalUnit, count = 12) {
11
+ if (!nextDueDate || !intervalValue || !intervalUnit) return [];
12
+ const occurrences = [];
13
+ let cursor = new Date(nextDueDate);
14
+ for (let i = 0; i < count; i++) {
15
+ occurrences.push(new Date(cursor));
16
+ switch (intervalUnit) {
17
+ case 'days':
18
+ cursor.setDate(cursor.getDate() + intervalValue);
19
+ break;
20
+ case 'weeks':
21
+ cursor.setDate(cursor.getDate() + intervalValue * 7);
22
+ break;
23
+ case 'months':
24
+ cursor.setMonth(cursor.getMonth() + intervalValue);
25
+ break;
26
+ case 'years':
27
+ cursor.setFullYear(cursor.getFullYear() + intervalValue);
28
+ break;
29
+ default:
30
+ cursor.setDate(cursor.getDate() + intervalValue);
31
+ }
32
+ }
33
+ return occurrences;
34
+ }
35
+
36
+ /**
37
+ * Build a 6-week calendar grid for a given year/month.
38
+ * Each cell: { date, day, isCurrentMonth, isToday, isScheduled }
39
+ */
40
+ function buildCalendarGrid(year, month, scheduledDates) {
41
+ const scheduledSet = new Set(scheduledDates.map((d) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`));
42
+ const today = new Date();
43
+ const todayKey = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
44
+ const firstDay = new Date(year, month, 1);
45
+ const cursor = new Date(year, month, 1 - firstDay.getDay());
46
+ const weeks = [];
47
+ for (let w = 0; w < 6; w++) {
48
+ const week = [];
49
+ for (let d = 0; d < 7; d++) {
50
+ const date = new Date(cursor);
51
+ const key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
52
+ week.push({ date, day: date.getDate(), isCurrentMonth: date.getMonth() === month, isToday: key === todayKey, isScheduled: scheduledSet.has(key) });
53
+ cursor.setDate(cursor.getDate() + 1);
54
+ }
55
+ weeks.push(week);
56
+ }
57
+ return weeks;
58
+ }
59
+
60
+ const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
61
+ const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
62
+
63
+ export default class MaintenanceScheduleDetailsComponent extends Component {
64
+ @service fetch;
65
+ @tracked calendarYear;
66
+ @tracked calendarMonth;
67
+
68
+ constructor() {
69
+ super(...arguments);
70
+ const seed = this.args.resource?.next_due_date ? new Date(this.args.resource.next_due_date) : new Date();
71
+ this.calendarYear = seed.getFullYear();
72
+ this.calendarMonth = seed.getMonth();
73
+ }
74
+
75
+ get resource() {
76
+ return this.args.resource;
77
+ }
78
+
79
+ get occurrences() {
80
+ const r = this.resource;
81
+ if (!r) return [];
82
+ return computeOccurrences(r.next_due_date, r.interval_value, r.interval_unit, 12);
83
+ }
84
+
85
+ get calendarMonthName() {
86
+ return `${MONTH_NAMES[this.calendarMonth]} ${this.calendarYear}`;
87
+ }
88
+ get dayNames() {
89
+ return DAY_NAMES;
90
+ }
91
+
92
+ get calendarWeeks() {
93
+ return buildCalendarGrid(this.calendarYear, this.calendarMonth, this.occurrences);
94
+ }
95
+
96
+ get upcomingOccurrences() {
97
+ const now = new Date();
98
+ return this.occurrences.filter((d) => d >= now).slice(0, 6);
99
+ }
100
+
101
+ get isTimeBased() {
102
+ const m = this.resource?.interval_method;
103
+ return !m || m === 'time';
104
+ }
105
+
106
+ @action prevMonth() {
107
+ if (this.calendarMonth === 0) {
108
+ this.calendarMonth = 11;
109
+ this.calendarYear = this.calendarYear - 1;
110
+ } else {
111
+ this.calendarMonth = this.calendarMonth - 1;
112
+ }
113
+ }
114
+
115
+ @action nextMonth() {
116
+ if (this.calendarMonth === 11) {
117
+ this.calendarMonth = 0;
118
+ this.calendarYear = this.calendarYear + 1;
119
+ } else {
120
+ this.calendarMonth = this.calendarMonth + 1;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Trigger a browser download of the .ics file for this schedule.
126
+ * Uses fetch.download() which automatically attaches auth headers and
127
+ * reads the Content-Disposition / Content-Type from the response.
128
+ */
129
+ @action downloadIcal(schedule) {
130
+ const id = schedule.public_id ?? schedule.id;
131
+ this.fetch
132
+ .download(
133
+ `maintenance-schedules/${id}/ical`,
134
+ {},
135
+ {
136
+ fileName: `maintenance-schedule-${id}.ics`,
137
+ mimeType: 'text/calendar',
138
+ }
139
+ )
140
+ .catch((error) => {
141
+ // eslint-disable-next-line no-console
142
+ console.error('Failed to download iCal:', error);
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Open the Google Calendar "add event" URL for this schedule's next due date.
148
+ */
149
+ @action addToGoogleCalendar(schedule) {
150
+ const title = encodeURIComponent(schedule.name ?? 'Maintenance Schedule');
151
+ const dueDate = schedule.next_due_date ? new Date(schedule.next_due_date) : new Date();
152
+ const pad = (n) => String(n).padStart(2, '0');
153
+ const dateStr = `${dueDate.getFullYear()}${pad(dueDate.getMonth() + 1)}${pad(dueDate.getDate())}`;
154
+ const details = encodeURIComponent(schedule.description ?? schedule.instructions ?? '');
155
+ const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${title}&dates=${dateStr}/${dateStr}&details=${details}`;
156
+ window.open(url, '_blank', 'noopener,noreferrer');
157
+ }
158
+ }