@fleetbase/fleetops-engine 0.6.64 → 0.6.65

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 (284) hide show
  1. package/RELEASE.md +7 -5
  2. package/addon/components/attachment-list.hbs +65 -0
  3. package/addon/components/attachment-list.js +8 -0
  4. package/addon/components/cell/attached-vehicle.hbs +1 -1
  5. package/addon/components/cell/attached-vehicle.js +3 -3
  6. package/addon/components/cell/driver-identity.js +4 -4
  7. package/addon/components/cell/driver-name.hbs +1 -1
  8. package/addon/components/cell/translated-value.hbs +9 -0
  9. package/addon/components/cell/translated-value.js +57 -0
  10. package/addon/components/contact/card.hbs +58 -0
  11. package/addon/components/contact/card.js +19 -0
  12. package/addon/components/contact/form.hbs +1 -1
  13. package/addon/components/contact/panel-header.hbs +1 -1
  14. package/addon/components/customer/form.hbs +2 -2
  15. package/addon/components/device/manager.hbs +26 -28
  16. package/addon/components/device/manager.js +28 -2
  17. package/addon/components/device/panel-tabs/vehicle.hbs +13 -13
  18. package/addon/components/device/panel-tabs/vehicle.js +8 -0
  19. package/addon/components/driver/card.hbs +24 -13
  20. package/addon/components/driver/card.js +20 -6
  21. package/addon/components/driver/form.hbs +3 -3
  22. package/addon/components/driver/panel-header.hbs +2 -2
  23. package/addon/components/driver/pill.hbs +2 -2
  24. package/addon/components/equipment/form.js +11 -4
  25. package/addon/components/equipment/pill.hbs +20 -0
  26. package/addon/components/equipment/pill.js +3 -0
  27. package/addon/components/filter/multi-model.hbs +19 -0
  28. package/addon/components/filter/multi-model.js +90 -0
  29. package/addon/components/fleet/driver-listing.hbs +1 -1
  30. package/addon/components/fleet/vehicle-listing.hbs +1 -1
  31. package/addon/components/fleet-panel/driver-listing.hbs +1 -1
  32. package/addon/components/fleet-panel/vehicle-listing.hbs +1 -1
  33. package/addon/components/layout/fleet-ops-sidebar.js +9 -7
  34. package/addon/components/maintenance/form.js +12 -4
  35. package/addon/components/maintenance-schedule/form.js +13 -7
  36. package/addon/components/map/leaflet-live-map.hbs +52 -0
  37. package/addon/components/map/order-list-overlay/order.hbs +4 -4
  38. package/addon/components/modals/attach-equipment.hbs +22 -0
  39. package/addon/components/modals/attach-equipment.js +3 -0
  40. package/addon/components/modals/attach-telematic-device.hbs +13 -8
  41. package/addon/components/modals/attach-telematic-device.js +11 -1
  42. package/addon/components/modals/attach-trailer.hbs +30 -0
  43. package/addon/components/modals/attach-trailer.js +3 -0
  44. package/addon/components/modals/contact-form.hbs +2 -2
  45. package/addon/components/modals/convert-customer-to-vendor.hbs +1 -1
  46. package/addon/components/modals/driver-form.hbs +2 -2
  47. package/addon/components/modals/select-trailer.hbs +35 -0
  48. package/addon/components/modals/select-trailer.js +3 -0
  49. package/addon/components/modals/vehicle-form.hbs +2 -2
  50. package/addon/components/orchestrator/resource-panel.hbs +2 -2
  51. package/addon/components/order/customer-avatar-stack.hbs +2 -2
  52. package/addon/components/order/details/detail.hbs +4 -4
  53. package/addon/components/order/details/payload.hbs +2 -2
  54. package/addon/components/order/kanban-card.hbs +4 -4
  55. package/addon/components/order-list-overlay/order.hbs +4 -4
  56. package/addon/components/trailer/card.hbs +68 -0
  57. package/addon/components/trailer/card.js +22 -0
  58. package/addon/components/trailer/details/connections.hbs +57 -0
  59. package/addon/components/trailer/details/connections.js +59 -0
  60. package/addon/components/trailer/details/equipment.hbs +25 -0
  61. package/addon/components/trailer/details/equipment.js +52 -0
  62. package/addon/components/trailer/details/maintenance-history.hbs +47 -0
  63. package/addon/components/trailer/details/maintenance-history.js +40 -0
  64. package/addon/components/trailer/details/schedules.hbs +45 -0
  65. package/addon/components/trailer/details/schedules.js +40 -0
  66. package/addon/components/trailer/details/work-orders.hbs +47 -0
  67. package/addon/components/trailer/details/work-orders.js +40 -0
  68. package/addon/components/trailer/details.hbs +399 -0
  69. package/addon/components/trailer/details.js +118 -0
  70. package/addon/components/trailer/form.hbs +447 -0
  71. package/addon/components/trailer/form.js +165 -0
  72. package/addon/components/trailer/panel-header.hbs +45 -0
  73. package/addon/components/trailer/panel-header.js +3 -0
  74. package/addon/components/trailer/pill.hbs +21 -0
  75. package/addon/components/trailer/pill.js +3 -0
  76. package/addon/components/vehicle/card.hbs +42 -11
  77. package/addon/components/vehicle/card.js +31 -0
  78. package/addon/components/vehicle/details/equipment.hbs +25 -0
  79. package/addon/components/vehicle/details/equipment.js +55 -0
  80. package/addon/components/vehicle/details/trailers.hbs +35 -0
  81. package/addon/components/vehicle/details/trailers.js +55 -0
  82. package/addon/components/vehicle/form.hbs +2 -2
  83. package/addon/components/vehicle/panel-header.hbs +2 -2
  84. package/addon/components/vehicle/pill.hbs +2 -2
  85. package/addon/components/vendor/card.hbs +58 -0
  86. package/addon/components/vendor/card.js +6 -0
  87. package/addon/components/vendor/panel-header.hbs +2 -2
  88. package/addon/components/vendor/personnel-manager.hbs +2 -2
  89. package/addon/components/work-order/form.js +12 -4
  90. package/addon/controllers/connectivity/devices/index/details/vehicle.js +11 -0
  91. package/addon/controllers/connectivity/devices/index/details.js +3 -3
  92. package/addon/controllers/connectivity/devices/index.js +1 -0
  93. package/addon/controllers/connectivity/telematics/details/attachments.js +28 -47
  94. package/addon/controllers/connectivity/telematics/details/devices.js +1 -0
  95. package/addon/controllers/management/contacts/customers.js +4 -0
  96. package/addon/controllers/management/contacts/index.js +32 -0
  97. package/addon/controllers/management/trailers/index/details.js +119 -0
  98. package/addon/controllers/management/trailers/index/edit.js +67 -0
  99. package/addon/controllers/management/trailers/index/new.js +43 -0
  100. package/addon/controllers/management/trailers/index.js +599 -0
  101. package/addon/controllers/management/vehicles/index/details.js +16 -0
  102. package/addon/controllers/management/vehicles/index.js +78 -4
  103. package/addon/controllers/management/vendors/index.js +32 -0
  104. package/addon/controllers/operations/orders/index.js +3 -1
  105. package/addon/controllers/settings/custom-fields.js +8 -0
  106. package/addon/extension.js +7 -0
  107. package/addon/helpers/placeholder-image.js +6 -0
  108. package/addon/helpers/resource-image.js +10 -0
  109. package/addon/helpers/trailer-placeholder-image.js +6 -0
  110. package/addon/routes/management/trailers/index/details/connections.js +2 -0
  111. package/addon/routes/management/trailers/index/details/devices.js +2 -0
  112. package/addon/routes/management/trailers/index/details/equipment.js +2 -0
  113. package/addon/routes/management/trailers/index/details/index.js +2 -0
  114. package/addon/routes/management/trailers/index/details/maintenance-history.js +2 -0
  115. package/addon/routes/management/trailers/index/details/positions.js +2 -0
  116. package/addon/routes/management/trailers/index/details/schedules.js +2 -0
  117. package/addon/routes/management/trailers/index/details/work-orders.js +2 -0
  118. package/addon/routes/management/trailers/index/details.js +30 -0
  119. package/addon/routes/management/trailers/index/edit.js +23 -0
  120. package/addon/routes/management/trailers/index/new.js +14 -0
  121. package/addon/routes/management/trailers/index.js +42 -0
  122. package/addon/routes/management/vehicles/index/details/trailers.js +2 -0
  123. package/addon/routes/management/vehicles/index.js +7 -1
  124. package/addon/routes.js +17 -0
  125. package/addon/services/device-actions.js +26 -16
  126. package/addon/services/trailer-actions.js +394 -0
  127. package/addon/services/vehicle-actions.js +111 -0
  128. package/addon/styles/fleetops-engine.css +349 -0
  129. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +19 -19
  130. package/addon/templates/connectivity/telematics/details/attachments.hbs +15 -15
  131. package/addon/templates/management/contacts/customers.hbs +48 -29
  132. package/addon/templates/management/contacts/index.hbs +52 -33
  133. package/addon/templates/management/trailers/index/details/connections.hbs +1 -0
  134. package/addon/templates/management/trailers/index/details/devices.hbs +1 -0
  135. package/addon/templates/management/trailers/index/details/equipment.hbs +1 -0
  136. package/addon/templates/management/trailers/index/details/index.hbs +1 -0
  137. package/addon/templates/management/trailers/index/details/maintenance-history.hbs +1 -0
  138. package/addon/templates/management/trailers/index/details/positions.hbs +1 -0
  139. package/addon/templates/management/trailers/index/details/schedules.hbs +1 -0
  140. package/addon/templates/management/trailers/index/details/work-orders.hbs +1 -0
  141. package/addon/templates/management/trailers/index/details.hbs +15 -0
  142. package/addon/templates/management/trailers/index/edit.hbs +12 -0
  143. package/addon/templates/management/trailers/index/new.hbs +11 -0
  144. package/addon/templates/management/trailers/index.hbs +67 -0
  145. package/addon/templates/management/vehicles/index/details/equipment.hbs +1 -1
  146. package/addon/templates/management/vehicles/index/details/trailers.hbs +1 -0
  147. package/addon/templates/management/vehicles/index/details.hbs +1 -1
  148. package/addon/templates/management/vendors/index.hbs +66 -32
  149. package/addon/utils/device-table-columns.js +10 -6
  150. package/addon/utils/live-map-card-content.js +56 -0
  151. package/addon/utils/placeholder-images.js +71 -0
  152. package/addon/utils/trailer-placeholder-image.js +11 -0
  153. package/app/components/attachment-list.js +1 -0
  154. package/app/components/cell/translated-value.js +1 -0
  155. package/app/components/contact/card.js +1 -0
  156. package/app/components/equipment/pill.js +1 -0
  157. package/app/components/filter/multi-model.js +1 -0
  158. package/app/components/modals/attach-equipment.js +1 -0
  159. package/app/components/modals/attach-trailer.js +1 -0
  160. package/app/components/modals/select-trailer.js +1 -0
  161. package/app/components/trailer/card.js +1 -0
  162. package/app/components/trailer/details/connections.js +1 -0
  163. package/app/components/trailer/details/equipment.js +1 -0
  164. package/app/components/trailer/details/maintenance-history.js +1 -0
  165. package/app/components/trailer/details/schedules.js +1 -0
  166. package/app/components/trailer/details/work-orders.js +1 -0
  167. package/app/components/trailer/details.js +1 -0
  168. package/app/components/trailer/form.js +1 -0
  169. package/app/components/trailer/panel-header.js +1 -0
  170. package/app/components/trailer/pill.js +1 -0
  171. package/app/components/vehicle/details/equipment.js +1 -0
  172. package/app/components/vehicle/details/trailers.js +1 -0
  173. package/app/components/vendor/card.js +1 -0
  174. package/app/controllers/management/trailers/index/details.js +1 -0
  175. package/app/controllers/management/trailers/index/edit.js +1 -0
  176. package/app/controllers/management/trailers/index/new.js +1 -0
  177. package/app/controllers/management/trailers/index.js +1 -0
  178. package/app/helpers/placeholder-image.js +1 -0
  179. package/app/helpers/resource-image.js +1 -0
  180. package/app/helpers/trailer-placeholder-image.js +1 -0
  181. package/app/routes/management/trailers/index/details/connections.js +1 -0
  182. package/app/routes/management/trailers/index/details/devices.js +1 -0
  183. package/app/routes/management/trailers/index/details/equipment.js +1 -0
  184. package/app/routes/management/trailers/index/details/index.js +1 -0
  185. package/app/routes/management/trailers/index/details/maintenance-history.js +1 -0
  186. package/app/routes/management/trailers/index/details/positions.js +1 -0
  187. package/app/routes/management/trailers/index/details/schedules.js +1 -0
  188. package/app/routes/management/trailers/index/details/work-orders.js +1 -0
  189. package/app/routes/management/trailers/index/details.js +1 -0
  190. package/app/routes/management/trailers/index/edit.js +1 -0
  191. package/app/routes/management/trailers/index/new.js +1 -0
  192. package/app/routes/management/trailers/index.js +1 -0
  193. package/app/routes/management/vehicles/index/details/trailers.js +1 -0
  194. package/app/services/trailer-actions.js +1 -0
  195. package/app/templates/management/trailers/index/details/connections.js +1 -0
  196. package/app/templates/management/trailers/index/details/devices.js +1 -0
  197. package/app/templates/management/trailers/index/details/equipment.js +1 -0
  198. package/app/templates/management/trailers/index/details/index.js +1 -0
  199. package/app/templates/management/trailers/index/details/maintenance-history.js +1 -0
  200. package/app/templates/management/trailers/index/details/positions.js +1 -0
  201. package/app/templates/management/trailers/index/details/schedules.js +1 -0
  202. package/app/templates/management/trailers/index/details/work-orders.js +1 -0
  203. package/app/templates/management/trailers/index/details.js +1 -0
  204. package/app/templates/management/trailers/index/edit.js +1 -0
  205. package/app/templates/management/trailers/index/new.js +1 -0
  206. package/app/templates/management/trailers/index.js +1 -0
  207. package/app/templates/management/vehicles/index/details/trailers.js +1 -0
  208. package/composer.json +1 -1
  209. package/extension.json +1 -1
  210. package/package.json +1 -1
  211. package/server/migrations/2026_09_03_000001_add_first_class_trailer_columns_to_assets_table.php +58 -0
  212. package/server/migrations/2026_09_03_000002_create_asset_connections_table.php +43 -0
  213. package/server/migrations/2026_09_03_000003_create_device_installations_table.php +33 -0
  214. package/server/src/Auth/Schemas/FleetOps.php +7 -0
  215. package/server/src/Events/TrailerLocationChanged.php +70 -0
  216. package/server/src/Exceptions/DeviceAlreadyAttachedException.php +18 -0
  217. package/server/src/Exports/TrailerExport.php +31 -0
  218. package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +8 -2
  219. package/server/src/Http/Controllers/Api/v1/DeviceController.php +22 -15
  220. package/server/src/Http/Controllers/Api/v1/EquipmentController.php +47 -0
  221. package/server/src/Http/Controllers/Api/v1/TrailerController.php +362 -0
  222. package/server/src/Http/Controllers/Api/v1/VehicleController.php +1 -0
  223. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +3 -0
  224. package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
  225. package/server/src/Http/Controllers/Internal/v1/SearchController.php +16 -0
  226. package/server/src/Http/Controllers/Internal/v1/TrailerController.php +360 -0
  227. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +74 -0
  228. package/server/src/Http/Filter/EquipmentFilter.php +51 -0
  229. package/server/src/Http/Filter/OrderFilter.php +15 -0
  230. package/server/src/Http/Filter/TrailerFilter.php +269 -0
  231. package/server/src/Http/Filter/VehicleFilter.php +70 -3
  232. package/server/src/Http/Requests/CreateDeviceRequest.php +1 -1
  233. package/server/src/Http/Requests/CreateEquipmentRequest.php +1 -1
  234. package/server/src/Http/Requests/CreateTrailerRequest.php +54 -0
  235. package/server/src/Http/Requests/UpdateTrailerRequest.php +7 -0
  236. package/server/src/Http/Resources/v1/AssetConnection.php +90 -0
  237. package/server/src/Http/Resources/v1/Device.php +3 -1
  238. package/server/src/Http/Resources/v1/Equipment.php +3 -1
  239. package/server/src/Http/Resources/v1/Index/Vehicle.php +10 -0
  240. package/server/src/Http/Resources/v1/Trailer.php +199 -0
  241. package/server/src/Http/Resources/v1/Vehicle.php +2 -0
  242. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +3 -0
  243. package/server/src/Imports/TrailerImport.php +33 -0
  244. package/server/src/Models/Asset.php +39 -10
  245. package/server/src/Models/AssetConnection.php +43 -0
  246. package/server/src/Models/Device.php +39 -8
  247. package/server/src/Models/DeviceInstallation.php +27 -0
  248. package/server/src/Models/Equipment.php +2 -0
  249. package/server/src/Models/Trailer.php +181 -0
  250. package/server/src/Models/Vehicle.php +13 -0
  251. package/server/src/Providers/FleetOpsServiceProvider.php +4 -2
  252. package/server/src/Support/Ai/FleetOpsAiQueryResources.php +22 -0
  253. package/server/src/Support/Reporting/FleetOpsReportSchema.php +38 -0
  254. package/server/src/Support/Telematics/TelematicService.php +35 -0
  255. package/server/src/Support/TrailerSummary.php +35 -0
  256. package/server/src/routes.php +29 -0
  257. package/server/tests/CompactResourceSerializationTest.php +36 -0
  258. package/server/tests/ControllerFilterContractsTest.php +29 -0
  259. package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +30 -13
  260. package/server/tests/Feature/Http/Api/TrailerControllerLifecycleTest.php +640 -0
  261. package/server/tests/Feature/Http/Internal/DeviceControllerContractsTest.php +20 -5
  262. package/server/tests/Feature/Http/Internal/LiveControllerEndpointsTest.php +19 -0
  263. package/server/tests/ModelAccessorContractsTest.php +10 -0
  264. package/server/tests/OrderFilterExecutionTest.php +5 -0
  265. package/server/tests/PublicApiControllerInputContractsTest.php +11 -0
  266. package/server/tests/PublicFleetOpsRestEndpointsTest.php +15 -3
  267. package/server/tests/ReportSchemaContractsTest.php +4 -0
  268. package/server/tests/TelematicsHardeningTest.php +4 -2
  269. package/server/tests/TrailerSupportCoverageTest.php +524 -0
  270. package/server/tests/Unit/Http/Resources/IndexVehicleResourceTest.php +35 -0
  271. package/server/tests/Unit/Models/EquipmentTest.php +22 -0
  272. package/server/tests/Unit/Models/ModelLookupFallbackSeamsTest.php +7 -3
  273. package/server/tests/Unit/Models/TrailerTest.php +204 -0
  274. package/server/tests/Unit/Support/Telematics/TelematicServiceTelemetryTest.php +93 -0
  275. package/server/tests/VehicleControllerHelperContractsTest.php +14 -0
  276. package/translations/ar-ae.yml +396 -0
  277. package/translations/bg-bg.yaml +424 -0
  278. package/translations/en-us.yaml +423 -0
  279. package/translations/es-pa.yaml +425 -0
  280. package/translations/fr-fr.yaml +424 -0
  281. package/translations/mn-mn.yaml +424 -0
  282. package/translations/pt-br.yaml +424 -0
  283. package/translations/ru-ru.yaml +424 -0
  284. package/translations/vi-vn.yaml +435 -0
@@ -0,0 +1,447 @@
1
+ <div class="form-wrapper trailer-form" ...attributes>
2
+ <RegistryYield @registry="fleet-ops:component:trailer:form:start" as |RegistryComponent|>
3
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
4
+ </RegistryYield>
5
+
6
+ {{! DETAILS / IDENTIFICATION }}
7
+ <ContentPanel @title={{t "trailer.sections.details"}} @open={{true}} @wrapperClass="bordered-top">
8
+ <div class="flex flex-row space-x-3 mb-3">
9
+ <div class="flex items-center justify-start rounded-full">
10
+ <Image
11
+ src={{or @resource.photo_url (trailer-placeholder-image)}}
12
+ @fallbackSrc={{(trailer-placeholder-image)}}
13
+ alt={{@resource.name}}
14
+ height="48"
15
+ width="48"
16
+ class="h-14 w-14 rounded-full shadow-sm"
17
+ />
18
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
19
+ <InputInfo @text={{t "trailer.help.photo"}} />
20
+ </Attach::Tooltip>
21
+ </div>
22
+ <div>
23
+ <UploadButton
24
+ @name={{t "trailer.fields.photo"}}
25
+ @accept="image/*"
26
+ @onFileAdded={{perform this.handlePhotoUpload}}
27
+ @icon="upload"
28
+ @size="xs"
29
+ @buttonText={{t "common.upload-image"}}
30
+ @disabled={{cannot-write @resource}}
31
+ />
32
+ <div class="px-1">
33
+ <span class="text-gray-300 dark:text-gray-600 text-xs">{{t "common.upload-image-supported"}}</span>
34
+ </div>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
39
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
40
+ {{t "trailer.sections.identification"}}
41
+ </div>
42
+
43
+ <InputGroup @name={{t "trailer.fields.name"}} @helpText={{t "trailer.help.name"}}>
44
+ <Input @value={{@resource.name}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.name"}} disabled={{cannot-write @resource}} required />
45
+ </InputGroup>
46
+
47
+ <InputGroup @name={{t "trailer.fields.code"}} @helpText={{t "trailer.help.code"}}>
48
+ <Input @value={{@resource.code}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.code"}} disabled={{cannot-write @resource}} />
49
+ </InputGroup>
50
+
51
+ <InputGroup @name={{t "trailer.fields.description"}} @wrapperClass="col-span-1 lg:col-span-2">
52
+ <Textarea @value={{@resource.description}} class="w-full form-input" rows="2" placeholder={{t "trailer.placeholders.description"}} disabled={{cannot-write @resource}} />
53
+ </InputGroup>
54
+
55
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
56
+ {{t "trailer.sections.classification"}}
57
+ </div>
58
+
59
+ <InputGroup @name={{t "trailer.fields.type"}} @helpText={{t "trailer.help.type"}}>
60
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
61
+ <PowerSelect
62
+ @options={{this.typeOptions}}
63
+ @selected={{find-by "value" @resource.type this.typeOptions}}
64
+ @onChange={{this.setType}}
65
+ @placeholder={{t "trailer.placeholders.type"}}
66
+ @triggerClass="form-select form-input"
67
+ @disabled={{cannot-write @resource}}
68
+ @searchEnabled={{true}}
69
+ @searchField="label"
70
+ as |option|
71
+ >
72
+ {{option.label}}
73
+ </PowerSelect>
74
+ </div>
75
+ </InputGroup>
76
+
77
+ <InputGroup @name={{t "trailer.fields.status"}} @helpText={{t "trailer.help.status"}}>
78
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
79
+ <PowerSelect
80
+ @options={{this.statusOptions}}
81
+ @selected={{find-by "value" @resource.status this.statusOptions}}
82
+ @onChange={{set-model-attr @resource "status"}}
83
+ @placeholder={{t "trailer.placeholders.status"}}
84
+ @triggerClass="form-select form-input"
85
+ @disabled={{cannot-write @resource}}
86
+ as |option|
87
+ >
88
+ <div class="text-sm">
89
+ <div class="font-semibold normalize-in-trigger">{{option.label}}</div>
90
+ <div class="hide-from-trigger">{{option.description}}</div>
91
+ </div>
92
+ </PowerSelect>
93
+ </div>
94
+ </InputGroup>
95
+
96
+ <InputGroup @name={{t "trailer.fields.body-type"}} @helpText={{t "trailer.help.body-type"}}>
97
+ <Input @value={{@resource.body_type}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.body-type"}} disabled={{cannot-write @resource}} />
98
+ </InputGroup>
99
+
100
+ <InputGroup @name={{t "trailer.fields.color"}}>
101
+ <Input @value={{@resource.color}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.color"}} disabled={{cannot-write @resource}} />
102
+ </InputGroup>
103
+
104
+ <InputGroup @name={{t "trailer.fields.category"}} @helpText={{t "trailer.help.category"}}>
105
+ <ModelSelect
106
+ @modelName="category"
107
+ @selectedModel={{@resource.category}}
108
+ @query={{hash for="trailer"}}
109
+ @placeholder={{t "trailer.placeholders.category"}}
110
+ @triggerClass="form-select form-input"
111
+ @infiniteScroll={{false}}
112
+ @renderInPlace={{true}}
113
+ @allowClear={{true}}
114
+ @onChange={{this.setCategory}}
115
+ @disabled={{cannot-write @resource}}
116
+ as |model|
117
+ >
118
+ {{model.name}}
119
+ </ModelSelect>
120
+ </InputGroup>
121
+
122
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
123
+ {{t "trailer.sections.registration"}}
124
+ </div>
125
+
126
+ <InputGroup @name={{t "trailer.fields.vin"}} @helpText={{t "trailer.help.vin"}}>
127
+ <Input @value={{@resource.vin}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.vin"}} disabled={{cannot-write @resource}} />
128
+ </InputGroup>
129
+
130
+ <InputGroup @name={{t "trailer.fields.plate-number"}}>
131
+ <Input @value={{@resource.plate_number}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.plate-number"}} disabled={{cannot-write @resource}} />
132
+ </InputGroup>
133
+
134
+ <InputGroup @name={{t "trailer.fields.serial-number"}}>
135
+ <Input @value={{@resource.serial_number}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.serial-number"}} disabled={{cannot-write @resource}} />
136
+ </InputGroup>
137
+
138
+ <InputGroup @name={{t "trailer.fields.make"}}>
139
+ <Input @value={{@resource.make}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.make"}} disabled={{cannot-write @resource}} />
140
+ </InputGroup>
141
+
142
+ <InputGroup @name={{t "trailer.fields.model"}}>
143
+ <Input @value={{@resource.model}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.model"}} disabled={{cannot-write @resource}} />
144
+ </InputGroup>
145
+
146
+ <InputGroup @name={{t "trailer.fields.year"}}>
147
+ <Input @value={{@resource.year}} @type="number" min="1880" step="1" class="w-full form-input" placeholder={{t "trailer.placeholders.year"}} disabled={{cannot-write @resource}} />
148
+ </InputGroup>
149
+
150
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
151
+ {{t "trailer.sections.location"}}
152
+ </div>
153
+
154
+ <InputGroup @name={{t "trailer.fields.coordinates"}} @wrapperClass="col-span-1 lg:col-span-2" @helpText={{t "trailer.help.coordinates"}}>
155
+ <ModelCoordinatesInput @model={{@resource}} @disabled={{cannot-write @resource}} @autocomplete="location" />
156
+ </InputGroup>
157
+ </div>
158
+
159
+ <RegistryYield @registry="fleet-ops:component:trailer:form:details" as |RegistryComponent|>
160
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
161
+ </RegistryYield>
162
+ </ContentPanel>
163
+
164
+ {{! DIMENSIONS & CAPACITY }}
165
+ <ContentPanel @title={{t "trailer.sections.dimensions"}} @open={{true}} @wrapperClass="bordered-top">
166
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
167
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-1">
168
+ {{t "trailer.sections.measurement"}}
169
+ </div>
170
+
171
+ <InputGroup @name={{t "trailer.fields.measurement-system"}} @helpText={{t "trailer.help.measurement-system"}}>
172
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
173
+ <PowerSelect
174
+ @options={{this.measurementOptions}}
175
+ @selected={{find-by "value" @resource.measurement_system this.measurementOptions}}
176
+ @onChange={{set-model-attr @resource "measurement_system"}}
177
+ @placeholder={{t "trailer.placeholders.measurement-system"}}
178
+ @triggerClass="form-select form-input"
179
+ @disabled={{cannot-write @resource}}
180
+ as |option|
181
+ >
182
+ <div class="text-sm">
183
+ <div class="font-semibold normalize-in-trigger">{{option.label}}</div>
184
+ <div class="hide-from-trigger">{{option.description}}</div>
185
+ </div>
186
+ </PowerSelect>
187
+ </div>
188
+ </InputGroup>
189
+
190
+ <InputGroup @name={{t "trailer.fields.odometer-unit"}}>
191
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
192
+ <PowerSelect
193
+ @options={{this.odometerUnitOptions}}
194
+ @selected={{find-by "value" @resource.odometer_unit this.odometerUnitOptions}}
195
+ @onChange={{set-model-attr @resource "odometer_unit"}}
196
+ @placeholder={{t "trailer.placeholders.odometer-unit"}}
197
+ @triggerClass="form-select form-input"
198
+ @disabled={{cannot-write @resource}}
199
+ as |option|
200
+ >
201
+ {{option.label}}
202
+ </PowerSelect>
203
+ </div>
204
+ </InputGroup>
205
+
206
+ <InputGroup @name={{t "trailer.fields.odometer" unit=(or @resource.odometer_unit "")}} @helpText={{t "trailer.help.odometer"}}>
207
+ <Input @value={{@resource.odometer}} @type="number" min="0" step="1" class="w-full form-input" placeholder={{t "trailer.placeholders.odometer"}} disabled={{cannot-write @resource}} />
208
+ </InputGroup>
209
+
210
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
211
+ {{t "trailer.sections.dimensions-fields" unit=this.units.length}}
212
+ </div>
213
+
214
+ <InputGroup @name={{t "trailer.fields.length" unit=this.units.length}}>
215
+ <Input @value={{@resource.length}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.length"}} disabled={{cannot-write @resource}} />
216
+ </InputGroup>
217
+
218
+ <InputGroup @name={{t "trailer.fields.width" unit=this.units.length}}>
219
+ <Input @value={{@resource.width}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.width"}} disabled={{cannot-write @resource}} />
220
+ </InputGroup>
221
+
222
+ <InputGroup @name={{t "trailer.fields.height" unit=this.units.length}}>
223
+ <Input @value={{@resource.height}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.height"}} disabled={{cannot-write @resource}} />
224
+ </InputGroup>
225
+
226
+ <InputGroup @name={{t "trailer.fields.cargo-volume" unit=this.units.volume}} @helpText={{t "trailer.help.cargo-volume"}}>
227
+ <Input @value={{@resource.cargo_volume}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.cargo-volume"}} disabled={{cannot-write @resource}} />
228
+ </InputGroup>
229
+
230
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
231
+ {{t "trailer.sections.weight" unit=this.units.weight}}
232
+ </div>
233
+
234
+ <InputGroup @name={{t "trailer.fields.tare-weight" unit=this.units.weight}} @helpText={{t "trailer.help.tare-weight"}}>
235
+ <Input @value={{@resource.tare_weight}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.tare-weight"}} disabled={{cannot-write @resource}} />
236
+ </InputGroup>
237
+
238
+ <InputGroup @name={{t "trailer.fields.gvwr" unit=this.units.weight}} @helpText={{t "trailer.help.gvwr"}}>
239
+ <Input @value={{@resource.gvwr}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.gvwr"}} disabled={{cannot-write @resource}} />
240
+ </InputGroup>
241
+
242
+ <InputGroup @name={{t "trailer.fields.payload-capacity" unit=this.units.weight}} @helpText={{t "trailer.help.payload-capacity"}}>
243
+ <Input @value={{@resource.payload_capacity}} @type="number" min="0" step="0.001" class="w-full form-input" placeholder={{t "trailer.placeholders.payload-capacity"}} disabled={{cannot-write @resource}} />
244
+ </InputGroup>
245
+ </div>
246
+ </ContentPanel>
247
+
248
+ {{! RUNNING GEAR }}
249
+ <ContentPanel @title={{t "trailer.sections.running-gear"}} @open={{true}} @wrapperClass="bordered-top">
250
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
251
+ <InputGroup @name={{t "trailer.fields.axle-count"}}>
252
+ <Input @value={{@resource.axle_count}} @type="number" min="0" step="1" class="w-full form-input" placeholder={{t "trailer.placeholders.axle-count"}} disabled={{cannot-write @resource}} />
253
+ </InputGroup>
254
+
255
+ <InputGroup @name={{t "trailer.fields.tire-count"}}>
256
+ <Input @value={{@resource.tire_count}} @type="number" min="0" step="1" class="w-full form-input" placeholder={{t "trailer.placeholders.tire-count"}} disabled={{cannot-write @resource}} />
257
+ </InputGroup>
258
+
259
+ <InputGroup @name={{t "trailer.fields.door-count"}}>
260
+ <Input @value={{@resource.door_count}} @type="number" min="0" step="1" class="w-full form-input" placeholder={{t "trailer.placeholders.door-count"}} disabled={{cannot-write @resource}} />
261
+ </InputGroup>
262
+
263
+ <InputGroup @name={{t "trailer.fields.coupling-type"}} @helpText={{t "trailer.help.coupling-type"}}>
264
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
265
+ <PowerSelect
266
+ @options={{this.couplingOptions}}
267
+ @selected={{find-by "value" @resource.coupling_type this.couplingOptions}}
268
+ @onChange={{set-model-attr @resource "coupling_type"}}
269
+ @placeholder={{t "trailer.placeholders.coupling-type"}}
270
+ @triggerClass="form-select form-input"
271
+ @disabled={{cannot-write @resource}}
272
+ as |option|
273
+ >
274
+ {{option.label}}
275
+ </PowerSelect>
276
+ </div>
277
+ </InputGroup>
278
+
279
+ <InputGroup @name={{t "trailer.fields.brake-type"}}>
280
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
281
+ <PowerSelect
282
+ @options={{this.brakeOptions}}
283
+ @selected={{find-by "value" @resource.brake_type this.brakeOptions}}
284
+ @onChange={{set-model-attr @resource "brake_type"}}
285
+ @placeholder={{t "trailer.placeholders.brake-type"}}
286
+ @triggerClass="form-select form-input"
287
+ @disabled={{cannot-write @resource}}
288
+ as |option|
289
+ >
290
+ {{option.label}}
291
+ </PowerSelect>
292
+ </div>
293
+ </InputGroup>
294
+
295
+ <div class="grid grid-cols-2 gap-2">
296
+ <InputGroup @name={{t "trailer.fields.abs"}} @helpText={{t "trailer.help.abs"}}>
297
+ <Toggle @isToggled={{@resource.abs_equipped}} @onToggle={{set-model-attr @resource "abs_equipped"}} @disabled={{cannot-write @resource}} />
298
+ </InputGroup>
299
+
300
+ <InputGroup @name={{t "trailer.fields.ebs"}} @helpText={{t "trailer.help.ebs"}}>
301
+ <Toggle @isToggled={{@resource.ebs_equipped}} @onToggle={{set-model-attr @resource "ebs_equipped"}} @disabled={{cannot-write @resource}} />
302
+ </InputGroup>
303
+ </div>
304
+ </div>
305
+ </ContentPanel>
306
+
307
+ {{! REFRIGERATION }}
308
+ <ContentPanel @title={{t "trailer.sections.refrigeration"}} @open={{this.showRefrigeration}} @wrapperClass="bordered-top">
309
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
310
+ <InputGroup @name={{t "trailer.fields.refrigerated"}} @helpText={{t "trailer.help.refrigerated"}} @wrapperClass="col-span-1 lg:col-span-2">
311
+ <Toggle @isToggled={{@resource.refrigerated}} @onToggle={{this.toggleRefrigerated}} @disabled={{cannot-write @resource}} />
312
+ </InputGroup>
313
+
314
+ {{#if this.showRefrigeration}}
315
+ <InputGroup @name={{t "trailer.fields.temperature-min" unit=this.units.temperature}} @helpText={{t "trailer.help.temperature"}}>
316
+ <Input @value={{@resource.temperature_min}} @type="number" step="0.1" class="w-full form-input" placeholder={{t "trailer.placeholders.temperature-min"}} disabled={{cannot-write @resource}} />
317
+ </InputGroup>
318
+
319
+ <InputGroup @name={{t "trailer.fields.temperature-max" unit=this.units.temperature}}>
320
+ <Input @value={{@resource.temperature_max}} @type="number" step="0.1" class="w-full form-input" placeholder={{t "trailer.placeholders.temperature-max"}} disabled={{cannot-write @resource}} />
321
+ </InputGroup>
322
+
323
+ <InputGroup @name={{t "trailer.fields.reefer-hours"}} @helpText={{t "trailer.help.reefer-hours"}}>
324
+ <Input @value={{@resource.reefer_engine_hours}} @type="number" min="0" step="0.1" class="w-full form-input" placeholder={{t "trailer.placeholders.reefer-hours"}} disabled={{cannot-write @resource}} />
325
+ </InputGroup>
326
+ {{/if}}
327
+ </div>
328
+ </ContentPanel>
329
+
330
+ {{! OWNERSHIP & FINANCE }}
331
+ <ContentPanel @title={{t "trailer.sections.ownership"}} @open={{false}} @wrapperClass="bordered-top">
332
+ <div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
333
+ <InputGroup @name={{t "trailer.fields.ownership-type"}} @helpText={{t "trailer.help.ownership-type"}}>
334
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
335
+ <PowerSelect
336
+ @options={{this.ownershipOptions}}
337
+ @selected={{find-by "value" @resource.ownership_type this.ownershipOptions}}
338
+ @onChange={{set-model-attr @resource "ownership_type"}}
339
+ @placeholder={{t "trailer.placeholders.ownership-type"}}
340
+ @triggerClass="form-select form-input"
341
+ @disabled={{cannot-write @resource}}
342
+ as |option|
343
+ >
344
+ {{option.label}}
345
+ </PowerSelect>
346
+ </div>
347
+ </InputGroup>
348
+
349
+ <InputGroup @name={{t "trailer.fields.financing-status"}}>
350
+ <Input @value={{@resource.financing_status}} @type="text" class="w-full form-input" placeholder={{t "trailer.placeholders.financing-status"}} disabled={{cannot-write @resource}} />
351
+ </InputGroup>
352
+
353
+ <InputGroup @name={{t "trailer.fields.vendor"}} @helpText={{t "trailer.help.vendor"}}>
354
+ <ModelSelect
355
+ @modelName="vendor"
356
+ @selectedModel={{@resource.vendor}}
357
+ @placeholder={{t "trailer.placeholders.vendor"}}
358
+ @triggerClass="form-select form-input"
359
+ @infiniteScroll={{false}}
360
+ @renderInPlace={{true}}
361
+ @allowClear={{true}}
362
+ @onChange={{this.setVendor}}
363
+ @disabled={{cannot-write @resource}}
364
+ as |model|
365
+ >
366
+ {{model.name}}
367
+ </ModelSelect>
368
+ </InputGroup>
369
+
370
+ <InputGroup @name={{t "trailer.fields.warranty"}} @helpText={{t "trailer.help.warranty"}}>
371
+ <ModelSelect
372
+ @modelName="warranty"
373
+ @selectedModel={{@resource.warranty}}
374
+ @placeholder={{t "trailer.placeholders.warranty"}}
375
+ @triggerClass="form-select form-input"
376
+ @infiniteScroll={{false}}
377
+ @renderInPlace={{true}}
378
+ @allowClear={{true}}
379
+ @onChange={{this.setWarranty}}
380
+ @disabled={{cannot-write @resource}}
381
+ as |model|
382
+ >
383
+ {{or model.name (concat model.policy_number " - " model.provider)}}
384
+ </ModelSelect>
385
+ </InputGroup>
386
+
387
+ <InputGroup @name={{t "trailer.fields.purchased-at"}}>
388
+ <DatePicker
389
+ @value={{or (format-date-fns @resource.purchased_at "dd-MM-yyyy") null}}
390
+ @onChange={{fn (mut @resource.purchased_at)}}
391
+ @placeholder={{t "trailer.placeholders.date"}}
392
+ @disabled={{cannot-write @resource}}
393
+ class="filter-date-input w-full flex-1"
394
+ />
395
+ </InputGroup>
396
+
397
+ {{#if this.showLeaseExpiry}}
398
+ <InputGroup @name={{t "trailer.fields.lease-expires-at"}} @helpText={{t "trailer.help.lease-expires-at"}}>
399
+ <DatePicker
400
+ @value={{or (format-date-fns @resource.lease_expires_at "dd-MM-yyyy") null}}
401
+ @onChange={{fn (mut @resource.lease_expires_at)}}
402
+ @placeholder={{t "trailer.placeholders.date"}}
403
+ @disabled={{cannot-write @resource}}
404
+ class="filter-date-input w-full flex-1"
405
+ />
406
+ </InputGroup>
407
+ {{/if}}
408
+
409
+ <div class="col-span-1 lg:col-span-2 text-[11px] uppercase tracking-wide text-gray-500 font-semibold mt-3">
410
+ {{t "trailer.sections.finance"}}
411
+ </div>
412
+
413
+ <InputGroup @name={{t "trailer.fields.currency"}}>
414
+ <CurrencySelect @currency={{this.currency}} @onCurrencyChange={{fn (mut @resource.currency)}} @triggerClass="w-full form-select" @disabled={{cannot-write @resource}} />
415
+ </InputGroup>
416
+
417
+ <InputGroup @name={{t "trailer.fields.acquisition-cost"}} @helpText={{t "trailer.help.acquisition-cost"}}>
418
+ <MoneyInput class="w-full" @value={{@resource.acquisition_cost}} @currency={{this.currency}} @onChange={{fn (mut @resource.acquisition_cost)}} @disabled={{cannot-write @resource}} />
419
+ </InputGroup>
420
+
421
+ <InputGroup @name={{t "trailer.fields.current-value"}}>
422
+ <MoneyInput class="w-full" @value={{@resource.current_value}} @currency={{this.currency}} @onChange={{fn (mut @resource.current_value)}} @disabled={{cannot-write @resource}} />
423
+ </InputGroup>
424
+
425
+ <InputGroup @name={{t "trailer.fields.insurance-value"}}>
426
+ <MoneyInput class="w-full" @value={{@resource.insurance_value}} @currency={{this.currency}} @onChange={{fn (mut @resource.insurance_value)}} @disabled={{cannot-write @resource}} />
427
+ </InputGroup>
428
+
429
+ <InputGroup @name={{t "trailer.fields.depreciation-rate"}} @helpText={{t "trailer.help.depreciation-rate"}}>
430
+ <Input @value={{@resource.depreciation_rate}} @type="number" min="0" max="100" step="0.01" class="w-full form-input" placeholder={{t "trailer.placeholders.depreciation-rate"}} disabled={{cannot-write @resource}} />
431
+ </InputGroup>
432
+ </div>
433
+ </ContentPanel>
434
+
435
+ {{! NOTES }}
436
+ <ContentPanel @title={{t "trailer.sections.notes"}} @open={{false}} @wrapperClass="bordered-top">
437
+ <InputGroup @name={{t "trailer.fields.notes"}} @wrapperClass="no-input-group-padding">
438
+ <Textarea @value={{@resource.notes}} class="w-full form-input" rows="4" placeholder={{t "trailer.placeholders.notes"}} disabled={{cannot-write @resource}} />
439
+ </InputGroup>
440
+ </ContentPanel>
441
+
442
+ <CustomField::Yield @subject={{@resource}} @wrapperClass="bordered-top" />
443
+
444
+ <RegistryYield @registry="fleet-ops:component:trailer:form:end" as |RegistryComponent|>
445
+ <RegistryComponent @resource={{@resource}} @controller={{@controller}} @permission={{get-write-permission @resource}} />
446
+ </RegistryYield>
447
+ </div>
@@ -0,0 +1,165 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { action } from '@ember/object';
4
+ import { task } from 'ember-concurrency';
5
+
6
+ /**
7
+ * Stable trailer classification values. Mirrors `Trailer::TYPES` on the server.
8
+ */
9
+ export const TRAILER_TYPES = [
10
+ 'dry_van',
11
+ 'reefer',
12
+ 'flatbed',
13
+ 'step_deck',
14
+ 'lowboy',
15
+ 'tanker',
16
+ 'bulk',
17
+ 'dump',
18
+ 'chassis',
19
+ 'curtain_side',
20
+ 'car_carrier',
21
+ 'livestock',
22
+ 'logging',
23
+ 'dolly',
24
+ 'specialty',
25
+ 'other',
26
+ ];
27
+
28
+ /**
29
+ * Lifecycle statuses. Mirrors `Trailer::STATUSES` on the server.
30
+ */
31
+ export const TRAILER_STATUSES = ['available', 'in_use', 'maintenance', 'out_of_service', 'retired'];
32
+
33
+ /**
34
+ * Ownership arrangements. Mirrors `Trailer::OWNERSHIP_TYPES` on the server.
35
+ */
36
+ export const TRAILER_OWNERSHIP_TYPES = ['owned', 'leased', 'financed', 'rented'];
37
+
38
+ export const TRAILER_COUPLING_TYPES = ['fifth_wheel', 'pintle_hook', 'ball_hitch', 'gooseneck', 'drawbar', 'other'];
39
+ export const TRAILER_BRAKE_TYPES = ['air', 'electric', 'hydraulic_surge', 'none', 'other'];
40
+ export const TRAILER_MEASUREMENT_SYSTEMS = ['metric', 'imperial'];
41
+ export const TRAILER_ODOMETER_UNITS = ['km', 'mi'];
42
+
43
+ export default class TrailerFormComponent extends Component {
44
+ @service fetch;
45
+ @service currentUser;
46
+ @service notifications;
47
+ @service intl;
48
+
49
+ get typeOptions() {
50
+ return TRAILER_TYPES.map((value) => ({ value, label: this.intl.t(`trailer.types.${value}`) }));
51
+ }
52
+
53
+ get statusOptions() {
54
+ return TRAILER_STATUSES.map((value) => ({ value, label: this.intl.t(`trailer.statuses.${value}`), description: this.intl.t(`trailer.status-descriptions.${value}`) }));
55
+ }
56
+
57
+ get measurementOptions() {
58
+ return TRAILER_MEASUREMENT_SYSTEMS.map((value) => ({
59
+ value,
60
+ label: this.intl.t(`trailer.measurement.${value}`),
61
+ description: this.intl.t(`trailer.measurement-descriptions.${value}`),
62
+ }));
63
+ }
64
+
65
+ get odometerUnitOptions() {
66
+ return TRAILER_ODOMETER_UNITS.map((value) => ({ value, label: this.intl.t(`trailer.odometer-units.${value}`) }));
67
+ }
68
+
69
+ get couplingOptions() {
70
+ return TRAILER_COUPLING_TYPES.map((value) => ({ value, label: this.intl.t(`trailer.coupling-types.${value}`) }));
71
+ }
72
+
73
+ get brakeOptions() {
74
+ return TRAILER_BRAKE_TYPES.map((value) => ({ value, label: this.intl.t(`trailer.brake-types.${value}`) }));
75
+ }
76
+
77
+ get ownershipOptions() {
78
+ return TRAILER_OWNERSHIP_TYPES.map((value) => ({ value, label: this.intl.t(`trailer.ownership-types.${value}`) }));
79
+ }
80
+
81
+ /**
82
+ * Unit suffixes shown next to dimensional inputs, derived from the selected
83
+ * measurement system so operators always know which unit a value is stored in.
84
+ */
85
+ get units() {
86
+ const system = this.args.resource?.measurement_system === 'imperial' ? 'imperial' : 'metric';
87
+
88
+ return {
89
+ length: this.intl.t(`trailer.units.length-${system}`),
90
+ weight: this.intl.t(`trailer.units.weight-${system}`),
91
+ volume: this.intl.t(`trailer.units.volume-${system}`),
92
+ temperature: this.intl.t(`trailer.units.temperature-${system}`),
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Reefer settings only make sense for temperature-controlled trailers.
98
+ */
99
+ get showRefrigeration() {
100
+ const resource = this.args.resource;
101
+
102
+ return Boolean(resource?.refrigerated) || resource?.type === 'reefer';
103
+ }
104
+
105
+ /**
106
+ * Lease expiry is only meaningful when the trailer is not owned outright.
107
+ */
108
+ get showLeaseExpiry() {
109
+ return ['leased', 'rented', 'financed'].includes(this.args.resource?.ownership_type);
110
+ }
111
+
112
+ get currency() {
113
+ return this.args.resource?.currency || this.currentUser?.company?.currency || this.currentUser?.currency || 'USD';
114
+ }
115
+
116
+ @action setType(option) {
117
+ this.args.resource.type = option?.value ?? null;
118
+
119
+ if (option?.value === 'reefer' && this.args.resource.refrigerated !== true) {
120
+ this.args.resource.refrigerated = true;
121
+ }
122
+ }
123
+
124
+ @action toggleRefrigerated(value) {
125
+ this.args.resource.refrigerated = Boolean(value);
126
+ }
127
+
128
+ @action setVendor(vendor) {
129
+ this.args.resource.vendor = vendor;
130
+ this.args.resource.vendor_uuid = vendor?.id ?? null;
131
+ }
132
+
133
+ @action setWarranty(warranty) {
134
+ this.args.resource.warranty = warranty;
135
+ this.args.resource.warranty_uuid = warranty?.id ?? null;
136
+ }
137
+
138
+ @action setCategory(category) {
139
+ this.args.resource.category = category;
140
+ this.args.resource.category_uuid = category?.id ?? null;
141
+ }
142
+
143
+ @task *handlePhotoUpload(file) {
144
+ try {
145
+ yield this.fetch.uploadFile.perform(
146
+ file,
147
+ {
148
+ path: `uploads/${this.currentUser.companyId}/trailers/${this.args.resource.id}`,
149
+ subject_uuid: this.args.resource.id,
150
+ subject_type: 'fleet-ops:trailer',
151
+ type: 'trailer_photo',
152
+ },
153
+ (uploadedFile) => {
154
+ this.args.resource.setProperties({
155
+ photo_uuid: uploadedFile.id,
156
+ photo_url: uploadedFile.url,
157
+ photo: uploadedFile,
158
+ });
159
+ }
160
+ );
161
+ } catch (error) {
162
+ this.notifications.error(this.intl.t('trailer.prompts.photo-upload-error', { message: error.message }));
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,45 @@
1
+ <div class="px-4 py-2">
2
+ <div class="flex flex-1 flex-row items-start justify-between">
3
+ <div class="flex flex-row space-x-3">
4
+ <div class="flex items-start justify-start rounded-full">
5
+ <Image
6
+ src={{resource-image @resource.photo_url "trailer"}}
7
+ @fallbackSrc={{(placeholder-image "trailer")}}
8
+ alt={{@resource.displayName}}
9
+ height="48"
10
+ width="48"
11
+ class="h-14 w-14 rounded-full shadow-sm"
12
+ />
13
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
14
+ <InputInfo @text={{@resource.displayName}} />
15
+ </Attach::Tooltip>
16
+ </div>
17
+ <div class="flex flex-col">
18
+ <h1 class="text-gray-900 dark:text-white font-semibold text-2xl">{{@resource.displayName}}</h1>
19
+ <div class="-mt-1 text-sm text-gray-700 dark:text-gray-400">
20
+ {{#if @resource.type}}{{t (concat "trailer.types." @resource.type)}}{{/if}}
21
+ {{#if (or @resource.plate_number @resource.code)}}<span class="mx-1">·</span>{{or @resource.plate_number @resource.code}}{{/if}}
22
+ </div>
23
+ <div class="mt-1 flex flex-row items-center space-x-2">
24
+ <Badge @status={{@resource.status}} @text={{t (concat "trailer.statuses." (or @resource.status "available"))}} />
25
+ <Badge @status={{if @resource.isAttached "attached" "detached"}} @icon={{if @resource.isAttached "link" "link-slash"}} @text={{t (concat "trailer.attachment." (if @resource.isAttached "attached" "detached"))}} />
26
+ <Badge @status={{or @resource.connectivity_status "never_connected"}} @text={{t (concat "trailer.connectivity." (or @resource.connectivity_status "never_connected"))}} />
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <div class="next-view-header-right">
31
+ <Layout::Resource::Panel::HeaderActions
32
+ @resource={{@resource}}
33
+ @saveTask={{@saveTask}}
34
+ @saveOptions={{@saveOptions}}
35
+ @saveDisabled={{@saveDisabled}}
36
+ @pojoResource={{@pojoResource}}
37
+ @authSchema={{@authSchema}}
38
+ @actionButtons={{@actionButtons}}
39
+ @onPressCancel={{@onPressCancel}}
40
+ >
41
+ {{yield}}
42
+ </Layout::Resource::Panel::HeaderActions>
43
+ </div>
44
+ </div>
45
+ </div>
@@ -0,0 +1,3 @@
1
+ import Component from '@glimmer/component';
2
+
3
+ export default class TrailerPanelHeaderComponent extends Component {}