@fleetbase/fleetops-engine 0.6.50 → 0.6.51

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 (159) hide show
  1. package/README.md +185 -67
  2. package/addon/components/contact/panel-header.hbs +11 -48
  3. package/addon/components/customer/details.hbs +1 -2
  4. package/addon/components/customer/form.hbs +63 -51
  5. package/addon/components/customer/form.js +27 -1
  6. package/addon/components/customer/orders.hbs +48 -0
  7. package/addon/components/customer/orders.js +67 -5
  8. package/addon/components/driver/form.hbs +1 -1
  9. package/addon/components/home/getting-started-guidance.hbs +113 -0
  10. package/addon/components/home/getting-started-guidance.js +107 -0
  11. package/addon/components/issue/details/comments.hbs +3 -0
  12. package/addon/components/issue/details/comments.js +3 -0
  13. package/addon/components/issue/details/correspondence.hbs +9 -0
  14. package/addon/components/issue/details/correspondence.js +83 -0
  15. package/addon/components/issue/details/documents.hbs +69 -0
  16. package/addon/components/issue/details/documents.js +85 -0
  17. package/addon/components/issue/details.hbs +220 -59
  18. package/addon/components/issue/details.js +245 -0
  19. package/addon/components/issue/form.hbs +19 -1
  20. package/addon/components/issue/panel-header.hbs +43 -0
  21. package/addon/components/issue/panel-header.js +22 -0
  22. package/addon/components/issue/timeline.hbs +40 -0
  23. package/addon/components/issue/timeline.js +45 -0
  24. package/addon/components/leaflet-tracking-marker.js +3 -0
  25. package/addon/components/map/drawer/position-listing.js +3 -0
  26. package/addon/components/map/leaflet-live-map.hbs +2 -2
  27. package/addon/components/map/leaflet-live-map.js +17 -0
  28. package/addon/components/map/order-list-overlay.hbs +4 -4
  29. package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
  30. package/addon/components/modals/convert-customer-to-vendor.js +55 -0
  31. package/addon/components/modals/issue-assign.hbs +20 -0
  32. package/addon/components/modals/issue-assign.js +49 -0
  33. package/addon/components/modals/issue-close.hbs +30 -0
  34. package/addon/components/modals/issue-close.js +73 -0
  35. package/addon/components/modals/issue-status.hbs +20 -0
  36. package/addon/components/modals/issue-status.js +43 -0
  37. package/addon/components/modals/reset-customer-credentials.js +12 -6
  38. package/addon/components/modals/update-order-activity.hbs +51 -17
  39. package/addon/components/modals/update-order-activity.js +59 -5
  40. package/addon/components/order/details/activity.js +2 -2
  41. package/addon/components/order/details/proof.hbs +40 -38
  42. package/addon/components/order/details/proof.js +31 -4
  43. package/addon/components/order/details.hbs +5 -5
  44. package/addon/components/order/details.js +19 -1
  45. package/addon/components/order/header.hbs +1 -1
  46. package/addon/components/route-list.hbs +0 -12
  47. package/addon/components/service-rate/details.hbs +1 -1
  48. package/addon/components/service-rate/form.hbs +1 -1
  49. package/addon/components/service-rate/form.js +4 -0
  50. package/addon/components/vendor/personnel-manager.hbs +166 -0
  51. package/addon/components/vendor/personnel-manager.js +143 -0
  52. package/addon/components/widget/live-fleet.hbs +1 -0
  53. package/addon/components/widget/live-fleet.js +2 -7
  54. package/addon/components/widget/revenue-trend.hbs +1 -1
  55. package/addon/components/widget/revenue-trend.js +75 -2
  56. package/addon/controllers/management/contacts/customers/details.js +31 -13
  57. package/addon/controllers/management/contacts/customers.js +16 -5
  58. package/addon/controllers/management/contacts/index/details.js +31 -13
  59. package/addon/controllers/management/contacts/index.js +7 -0
  60. package/addon/controllers/management/issues/index/details.js +52 -6
  61. package/addon/controllers/management/vendors/index/details.js +4 -0
  62. package/addon/controllers/operations/orders/index/details.js +11 -3
  63. package/addon/controllers/operations/orders/index.js +1 -1
  64. package/addon/extension.js +19 -12
  65. package/addon/helpers/format-duration.js +1 -38
  66. package/addon/initializers/load-leaflet-assets.js +5 -6
  67. package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
  68. package/addon/routes/management/issues/index/details.js +1 -1
  69. package/addon/routes.js +1 -0
  70. package/addon/services/contact-actions.js +231 -1
  71. package/addon/services/getting-started.js +44 -0
  72. package/addon/services/issue-actions.js +71 -0
  73. package/addon/services/leaflet-map-manager.js +2 -0
  74. package/addon/services/map-adapter/leaflet.js +19 -0
  75. package/addon/services/service-areas.js +3 -0
  76. package/addon/styles/fleetops-engine.css +1036 -0
  77. package/addon/templates/management/contacts/customers/details.hbs +1 -1
  78. package/addon/templates/management/contacts/index/details.hbs +1 -1
  79. package/addon/templates/management/contacts.hbs +2 -2
  80. package/addon/templates/management/issues/index/details/index.hbs +3 -1
  81. package/addon/templates/management/issues/index/details.hbs +3 -2
  82. package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
  83. package/addon/templates/operations/orders/index/details/index.hbs +2 -2
  84. package/addon/templates/operations/orders/index.hbs +1 -1
  85. package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
  86. package/addon/templates/operations/service-rates/index/new.hbs +1 -0
  87. package/addon/templates/settings/payments/onboard.hbs +8 -8
  88. package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
  89. package/addon/utils/leaflet-plugin-loader.js +219 -0
  90. package/addon/utils/setup-customer-portal.js +4 -64
  91. package/app/components/home/getting-started-guidance.js +1 -0
  92. package/app/components/issue/details/comments.js +1 -0
  93. package/app/components/issue/details/correspondence.js +1 -0
  94. package/app/components/issue/details/documents.js +1 -0
  95. package/app/components/issue/panel-header.js +1 -0
  96. package/app/components/modals/convert-customer-to-vendor.js +1 -0
  97. package/app/components/modals/issue-assign.js +1 -0
  98. package/app/components/modals/issue-close.js +1 -0
  99. package/app/components/modals/issue-status.js +1 -0
  100. package/app/components/vendor/personnel-manager.js +1 -0
  101. package/app/helpers/format-duration.js +1 -1
  102. package/app/services/getting-started.js +1 -0
  103. package/app/utils/leaflet-draw-namespace-guard.js +1 -0
  104. package/app/utils/leaflet-plugin-loader.js +1 -0
  105. package/composer.json +1 -1
  106. package/extension.json +1 -1
  107. package/package.json +5 -5
  108. package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
  109. package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
  110. package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
  111. package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
  112. package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
  113. package/server/resources/views/mail/customer-credentials.blade.php +16 -13
  114. package/server/src/Expansions/UserFilterExpansion.php +28 -0
  115. package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
  116. package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -25
  117. package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
  118. package/server/src/Http/Controllers/Internal/v1/ContactController.php +135 -0
  119. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
  120. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
  121. package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
  122. package/server/src/Http/Controllers/Internal/v1/OrderController.php +172 -97
  123. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
  124. package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
  125. package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
  126. package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
  127. package/server/src/Http/Resources/v1/Index/Order.php +12 -9
  128. package/server/src/Http/Resources/v1/Issue.php +25 -0
  129. package/server/src/Http/Resources/v1/Order.php +20 -18
  130. package/server/src/Mail/CustomerCredentialsMail.php +5 -6
  131. package/server/src/Models/Contact.php +30 -51
  132. package/server/src/Models/Issue.php +47 -6
  133. package/server/src/Models/Order.php +33 -6
  134. package/server/src/Models/ServiceArea.php +1 -1
  135. package/server/src/Models/VendorPersonnel.php +10 -1
  136. package/server/src/Models/Zone.php +1 -1
  137. package/server/src/Observers/PurchaseRateObserver.php +20 -0
  138. package/server/src/Observers/ZoneObserver.php +56 -0
  139. package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
  140. package/server/src/Support/Analytics/TopDrivers.php +9 -1
  141. package/server/src/Support/GettingStarted.php +144 -0
  142. package/server/src/Support/PlaceSearch.php +282 -0
  143. package/server/src/Support/ResolvesOrderServiceStops.php +22 -12
  144. package/server/src/routes.php +33 -1
  145. package/server/tests/AnalyticsRoutesTest.php +8 -0
  146. package/server/tests/CustomerContactIdentitySafetyTest.php +41 -0
  147. package/server/tests/DriverVehicleStatusDefaultTest.php +2 -2
  148. package/server/tests/GettingStartedStatusTest.php +21 -0
  149. package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
  150. package/server/tests/ServiceAreaZoneApiShapeTest.php +44 -0
  151. package/translations/ar-ae.yml +9 -0
  152. package/translations/bg-bg.yaml +9 -0
  153. package/translations/en-us.yaml +11 -0
  154. package/translations/es-pa.yaml +9 -0
  155. package/translations/fr-fr.yaml +9 -0
  156. package/translations/mn-mn.yaml +9 -0
  157. package/translations/pt-br.yaml +9 -0
  158. package/translations/ru-ru.yaml +9 -0
  159. package/translations/vi-vn.yaml +9 -0
@@ -21,7 +21,7 @@
21
21
  <div class="text-xs font-semibold uppercase">Updating activity for</div>
22
22
  <div class="mt-1 flex items-start justify-between gap-3">
23
23
  <div class="min-w-0">
24
- <div class="truncate text-sm font-semibold">{{or this.currentWaypoint.name this.currentWaypoint.id}}</div>
24
+ <div class="truncate text-sm font-semibold">{{or this.currentWaypoint.name this.currentWaypoint.public_id}}</div>
25
25
  <div class="truncate text-xs">{{or (place-address-html this.currentWaypoint showTitle=false) this.currentWaypoint.address this.currentWaypoint.street1}}</div>
26
26
  </div>
27
27
  {{#if this.currentWaypoint.status_code}}
@@ -111,24 +111,58 @@
111
111
  </fieldset>
112
112
 
113
113
  {{#if this.selectedActivityRequiresPod}}
114
- <div class="mb-5 rounded-md border border-yellow-300 bg-yellow-50 p-4 dark:border-yellow-700 dark:bg-yellow-900">
115
- <div class="mb-2 text-sm font-semibold text-yellow-900 dark:text-yellow-50">Photo proof of delivery required</div>
116
- <p class="mb-3 text-xs text-yellow-800 dark:text-yellow-100">Upload a delivery photo before submitting this activity update.</p>
117
- <FileUpload @name="photos[]" @accept={{join "," this.acceptedProofFileTypes}} @multiple={{false}} @onFileAdded={{this.setProofFile}}>
118
- <a tabindex={{0}} class="btn btn-magic cursor-pointer">
119
- <FaIcon @icon="camera" class="mr-1" />
120
- Select photo
121
- </a>
122
- </FileUpload>
114
+ <div class="mb-4 rounded-md border border-yellow-300 bg-yellow-50 py-2 text-yellow-900 dark:border-yellow-700 dark:bg-yellow-900 dark:text-yellow-50">
115
+ <div class="flex flex-wrap items-center justify-between gap-2 px-3">
116
+ <div class="min-w-0">
117
+ <div class="text-xs font-semibold uppercase">Photo proof required</div>
118
+ {{#if this.bypassProof}}
119
+ <div class="text-xs text-yellow-800 dark:text-yellow-100">Proof bypass enabled.</div>
120
+ {{else if this.hasUploadedProof}}
121
+ <div class="text-xs text-yellow-800 dark:text-yellow-100">Proof uploaded.</div>
122
+ {{else if this.uploadProofFile.isRunning}}
123
+ <div class="text-xs text-yellow-800 dark:text-yellow-100">Uploading proof...</div>
124
+ {{else}}
125
+ <div class="text-xs text-yellow-800 dark:text-yellow-100">Upload proof before submitting.</div>
126
+ {{/if}}
127
+ </div>
128
+ {{#unless this.bypassProof}}
129
+ <FileUpload @name="photos" @accept={{join "," this.acceptedProofFileTypes}} @multiple={{false}} @onFileAdded={{perform this.uploadProofFile}}>
130
+ <a tabindex={{0}} class="btn btn-magic btn-sm cursor-pointer">
131
+ {{#if this.uploadProofFile.isRunning}}
132
+ <Spinner class="mr-1" />
133
+ Uploading
134
+ {{else}}
135
+ <FaIcon @icon="camera" class="mr-1" />
136
+ Select photo
137
+ {{/if}}
138
+ </a>
139
+ </FileUpload>
140
+ {{/unless}}
141
+ </div>
142
+ <div class="px-3 mt-3 flex flex-wrap items-center justify-between gap-2 border-t border-yellow-50 pt-2 dark:border-yellow-700">
143
+ <Toggle @isToggled={{this.bypassProof}} @onToggle={{this.toggleBypassProof}} @label="Bypass proof of delivery" @labelClass="text-xs" />
144
+ <span class="text-xs text-yellow-800 dark:text-yellow-100">Use only when proof cannot be provided from the console.</span>
145
+ </div>
123
146
  {{#if this.proofFiles}}
124
- {{#each this.proofFiles as |file|}}
125
- <div class="mt-3 flex items-center justify-between rounded-md border border-yellow-400 bg-white px-3 py-2 text-xs text-yellow-900 dark:bg-yellow-950 dark:text-yellow-50">
126
- <span class="truncate">{{file.name}}</span>
127
- <span class="font-semibold">{{round file.progress}}%</span>
128
- </div>
129
- {{/each}}
147
+ <div class="mt-4 mb-1 px-3 space-x-2">
148
+ {{#each this.proofFiles as |file|}}
149
+ <div class="flex items-center justify-between gap-2 text-xs text-yellow-900 dark:text-yellow-50 border border-yellow-50 dark:border-yellow-700 shadow-sm px-2 py-1.5 rounded-md">
150
+ <div class="text-xs flex items-center space-x-2">
151
+ <FaIcon @icon="file" />
152
+ <span class="truncate">{{file.name}}</span>
153
+ </div>
154
+ <span class="flex-shrink-0 font-semibold">
155
+ {{#if this.hasUploadedProof}}
156
+ Uploaded
157
+ {{else}}
158
+ {{round file.progress}}%
159
+ {{/if}}
160
+ </span>
161
+ </div>
162
+ {{/each}}
163
+ </div>
130
164
  {{/if}}
131
165
  </div>
132
166
  {{/if}}
133
167
  </div>
134
- </Modal::Default>
168
+ </Modal::Default>
@@ -16,6 +16,8 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
16
16
  @tracked selectedActivity;
17
17
  @tracked customActivity = {};
18
18
  @tracked proofFiles = [];
19
+ @tracked proof;
20
+ @tracked bypassProof = false;
19
21
 
20
22
  constructor(owner, { options = {} }) {
21
23
  super(...arguments);
@@ -104,6 +106,14 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
104
106
  return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
105
107
  }
106
108
 
109
+ get hasUploadedProof() {
110
+ return Boolean(this.proof?.id || this.proof?.public_id);
111
+ }
112
+
113
+ get uploadedProofId() {
114
+ return this.proof?.id ?? this.proof?.public_id;
115
+ }
116
+
107
117
  @task *loadActivity() {
108
118
  if (!this.order?.id) {
109
119
  return [];
@@ -128,14 +138,22 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
128
138
  return this.notifications.warning(this.intl.t('fleet-ops.operations.orders.index.view.invalid-warning'));
129
139
  }
130
140
 
131
- if (this.selectedActivityRequiresPod && this.proofFiles.length === 0) {
141
+ if (this.selectedActivityRequiresPod && this.uploadProofFile.isRunning) {
142
+ modal.stopLoading();
143
+ return this.notifications.warning('Photo proof of delivery is still uploading.');
144
+ }
145
+
146
+ if (this.selectedActivityRequiresPod && !this.hasUploadedProof && !this.bypassProof) {
132
147
  modal.stopLoading();
133
- return this.notifications.warning('Photo proof of delivery is required for this activity.');
148
+ return this.notifications.warning('Upload photo proof of delivery or choose to bypass proof of delivery.');
134
149
  }
135
150
 
136
151
  try {
137
- const proof = this.selectedActivityRequiresPod ? yield this.capturePhotoProof.perform() : null;
138
- yield this.fetch.patch(`orders/update-activity/${this.order.id}`, { activity, proof: proof?.id ?? proof?.public_id });
152
+ yield this.fetch.patch(`orders/update-activity/${this.order.id}`, {
153
+ activity,
154
+ proof: this.hasUploadedProof ? this.uploadedProofId : null,
155
+ bypass_proof: this.selectedActivityRequiresPod && this.bypassProof,
156
+ });
139
157
 
140
158
  if (typeof this.order?.reload === 'function') {
141
159
  yield this.order.reload();
@@ -150,6 +168,7 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
150
168
  }
151
169
 
152
170
  this.modalsManager.setOption('activityCreated', activity);
171
+ this.modalsManager.setOption('proofCreated', this.hasUploadedProof ? this.proof : null);
153
172
  this.notifications.success(`Order activity has been updated to ${activity.status}`);
154
173
  modal.done();
155
174
  } catch (error) {
@@ -161,6 +180,10 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
161
180
 
162
181
  @task *capturePhotoProof() {
163
182
  const [proofFile] = this.proofFiles;
183
+ if (!proofFile) {
184
+ return null;
185
+ }
186
+
164
187
  const headers = this.fetch.getHeaders();
165
188
  delete headers['Content-Type'];
166
189
 
@@ -179,11 +202,42 @@ export default class ModalsUpdateOrderActivityComponent extends Component {
179
202
  return response.json();
180
203
  }
181
204
 
182
- @action setProofFile(file) {
205
+ @task *uploadProofFile(file) {
183
206
  if (!file) {
184
207
  return;
185
208
  }
186
209
 
210
+ this.bypassProof = false;
211
+ this.proof = null;
187
212
  this.proofFiles = [file];
213
+
214
+ try {
215
+ this.proof = this.normalizeProofResponse(yield this.capturePhotoProof.perform());
216
+ this.notifications.success('Photo proof of delivery uploaded.');
217
+ } catch (error) {
218
+ this.proof = null;
219
+ this.proofFiles = [];
220
+ this.notifications.serverError(error);
221
+ }
222
+ }
223
+
224
+ @action toggleBypassProof(value) {
225
+ this.bypassProof = value;
226
+ if (value) {
227
+ this.proofFiles = [];
228
+ this.proof = null;
229
+ }
230
+ }
231
+
232
+ normalizeProofResponse(response) {
233
+ if (response?.proof) {
234
+ return response.proof;
235
+ }
236
+
237
+ if (response?.data?.id || response?.data?.public_id) {
238
+ return response.data;
239
+ }
240
+
241
+ return response;
188
242
  }
189
243
  }
@@ -31,9 +31,9 @@ export default class OrderDetailsActivityComponent extends Component {
31
31
  disabled: this.args.resource.status === 'canceled',
32
32
  onClick: () => {
33
33
  this.orderActions.updateActivity(this.args.resource, {
34
- onFinish: ({ activityCreated }) => {
34
+ onFinish: (options) => {
35
35
  if (typeof this.args.onChange === 'function') {
36
- this.args.onChange(activityCreated);
36
+ this.args.onChange(options);
37
37
  }
38
38
  },
39
39
  });
@@ -1,42 +1,44 @@
1
- <ContentPanel @title={{t "order.fields.proof-of-delivery"}} @isLoading={{@isLoading}} @open={{true}} @wrapperClass="bordered-top">
2
- {{#if this.proofs}}
3
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-3">
4
- {{#each this.proofs as |proof|}}
5
- <div>
6
- {{#if (eq proof.type "photo")}}
7
- <a href={{proof.url}} target={{proof.id}}>
8
- <Image src={{proof.url}} class="w-48 h-48 rounded-md shadow-sm mb-2" />
9
- </a>
10
- {{/if}}
11
- {{#if (eq proof.type "signature")}}
12
- <a href={{proof.raw}} target={{proof.id}} download>
13
- <Image src={{proof.raw}} class="w-48 h-48 rounded-md shadow-sm mb-2" />
14
- </a>
15
- {{/if}}
16
- {{#if (eq proof.type "scan")}}
17
- <div class="flex items-center mb-2">
18
- <FaIcon @icon="qrcode" @size="2xl" class="text-blue-400" />
19
- </div>
20
- {{/if}}
21
- <div class="font-semibold text-sm mb-0.5">{{proof.remarks}}</div>
22
- <div class="text-xs">{{proof.public_id}}</div>
23
- <div class="text-xs">{{format-date-fns proof.created_at}}</div>
24
- </div>
25
- {{/each}}
26
- </div>
27
- {{else}}
28
- <div class="min-h-24 p-4">
29
- <div class="flex items-center justify-center border border-dashed border-gray-300 dark:border-gray-600 p-6 rounded-xl bg-gray-50 dark:bg-gray-800">
30
- <div class="text-center">
31
- <FaIcon @icon="camera-retro" @size="2x" class="text-gray-400 mb-2" />
32
- <div class="text-sm text-gray-600 dark:text-gray-400 font-medium">
33
- No proof of delivery
1
+ <div {{did-update this.reloadProofs @reloadToken}}>
2
+ <ContentPanel @title={{t "order.fields.proof-of-delivery"}} @isLoading={{@isLoading}} @open={{true}} @wrapperClass="bordered-top">
3
+ {{#if this.proofs}}
4
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-3">
5
+ {{#each this.proofs as |proof|}}
6
+ <div>
7
+ {{#if (eq proof.type "photo")}}
8
+ <a href={{proof.url}} target={{proof.id}}>
9
+ <Image src={{proof.url}} class="w-48 h-48 rounded-md shadow-sm mb-2" />
10
+ </a>
11
+ {{/if}}
12
+ {{#if (eq proof.type "signature")}}
13
+ <a href={{proof.raw}} target={{proof.id}} download>
14
+ <Image src={{proof.raw}} class="w-48 h-48 rounded-md shadow-sm mb-2" />
15
+ </a>
16
+ {{/if}}
17
+ {{#if (eq proof.type "scan")}}
18
+ <div class="flex items-center mb-2">
19
+ <FaIcon @icon="qrcode" @size="2xl" class="text-blue-400" />
20
+ </div>
21
+ {{/if}}
22
+ <div class="font-semibold text-sm mb-0.5">{{proof.remarks}}</div>
23
+ <div class="text-xs">{{proof.public_id}}</div>
24
+ <div class="text-xs">{{format-date-fns proof.created_at}}</div>
34
25
  </div>
35
- <div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
36
- Captured proof of delivery will show up here
26
+ {{/each}}
27
+ </div>
28
+ {{else}}
29
+ <div class="min-h-24 p-4">
30
+ <div class="flex items-center justify-center border border-dashed border-gray-300 dark:border-gray-600 p-6 rounded-xl bg-gray-50 dark:bg-gray-800">
31
+ <div class="text-center">
32
+ <FaIcon @icon="camera-retro" @size="2x" class="text-gray-400 mb-2" />
33
+ <div class="text-sm text-gray-600 dark:text-gray-400 font-medium">
34
+ No proof of delivery
35
+ </div>
36
+ <div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
37
+ Captured proof of delivery will show up here
38
+ </div>
37
39
  </div>
38
40
  </div>
39
41
  </div>
40
- </div>
41
- {{/if}}
42
- </ContentPanel>
42
+ {{/if}}
43
+ </ContentPanel>
44
+ </div>
@@ -1,5 +1,6 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
+ import { action } from '@ember/object';
3
4
  import { inject as service } from '@ember/service';
4
5
  import { task } from 'ember-concurrency-decorators';
5
6
 
@@ -13,12 +14,18 @@ export default class OrderDetailsProofComponent extends Component {
13
14
  }
14
15
 
15
16
  @task *loadOrderProofs(order) {
17
+ if (!order?.id) {
18
+ this.proofs = [];
19
+ return;
20
+ }
21
+
16
22
  const proofs = yield this.fetch.get(`orders/${order.id}/proofs`);
17
23
 
18
- this.proofs = proofs.map((proof) => ({
19
- ...proof,
20
- type: this.#getTypeFromRemarks(proof.remarks),
21
- }));
24
+ this.proofs = this.#normalizeProofs(proofs);
25
+ }
26
+
27
+ @action reloadProofs() {
28
+ this.loadOrderProofs.perform(this.args.resource);
22
29
  }
23
30
 
24
31
  #getTypeFromRemarks(remarks = '') {
@@ -27,4 +34,24 @@ export default class OrderDetailsProofComponent extends Component {
27
34
  if (remarks.endsWith('Signature')) return 'signature';
28
35
  return undefined;
29
36
  }
37
+
38
+ #normalizeProofs(proofs = []) {
39
+ const seen = new Set();
40
+
41
+ return proofs
42
+ .filter((proof) => {
43
+ const key = proof.uuid ?? proof.public_id ?? proof.id;
44
+ if (!key || seen.has(key)) {
45
+ return false;
46
+ }
47
+
48
+ seen.add(key);
49
+ return true;
50
+ })
51
+ .map((proof) => ({
52
+ ...proof,
53
+ type: this.#getTypeFromRemarks(proof.remarks),
54
+ }))
55
+ .sort((a, b) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime());
56
+ }
30
57
  }
@@ -2,12 +2,12 @@
2
2
  {{#if (has-block)}}
3
3
  {{yield
4
4
  (hash
5
- Activity=(component "order/details/activity" resource=@resource onChange=@onActivityChanged isLoadingActivity=@isLoadingActivity isLoading=@isLoading)
5
+ Activity=(component "order/details/activity" resource=@resource onChange=this.handleActivityChanged isLoadingActivity=@isLoadingActivity isLoading=@isLoading)
6
6
  Detail=(component "order/details/detail" resource=@resource onChange=@onDetailsChanged isLoading=@isLoading)
7
7
  CustomFields=(component "order/details/custom-fields" resource=@resource onChange=@onCustomFieldsChanged isLoading=@isLoading)
8
8
  PurchaseRate=(component "order/details/purchase-rate" resource=@resource isLoading=@isLoading)
9
9
  Tracking=(component "order/details/tracking" resource=@resource isLoading=@isLoading)
10
- Proof=(component "order/details/proof" resource=@resource isLoading=@isLoading)
10
+ Proof=(component "order/details/proof" resource=@resource reloadToken=this.effectiveProofReloadToken isLoading=@isLoading)
11
11
  Notes=(component "order/details/notes" resource=@resource onChange=@onNotesChanged isLoading=@isLoading)
12
12
  IntegratedVendorDetails=(component "order/details/integrated-vendor-details" resource=@resource isLoading=@isLoading)
13
13
  Route=(component "order/details/route" resource=@resource onChange=@onRouteChanged isLoading=@isLoading)
@@ -21,12 +21,12 @@
21
21
  )
22
22
  }}
23
23
  {{else}}
24
- <Order::Details::Activity @resource={{@resource}} @onChange={{@onActivityChanged}} @isLoadingActivity={{@isLoadingActivity}} @isLoading={{@isLoading}} />
24
+ <Order::Details::Activity @resource={{@resource}} @onChange={{this.handleActivityChanged}} @isLoadingActivity={{@isLoadingActivity}} @isLoading={{@isLoading}} />
25
25
  <Order::Details::Detail @resource={{@resource}} @onChange={{@onDetailsChanged}} @isLoading={{@isLoading}} />
26
26
  <Order::Details::CustomFields @resource={{@resource}} @onChange={{@onCustomFieldsChanged}} @isLoading={{@isLoading}} />
27
27
  <Order::Details::PurchaseRate @resource={{@resource}} @isLoading={{@isLoading}} />
28
28
  <Order::Details::Tracking @resource={{@resource}} @isLoading={{@isLoading}} />
29
- <Order::Details::Proof @resource={{@resource}} @isLoading={{@isLoading}} />
29
+ <Order::Details::Proof @resource={{@resource}} @reloadToken={{this.effectiveProofReloadToken}} @isLoading={{@isLoading}} />
30
30
  <Order::Details::Notes @resource={{@resource}} @onChange={{@onNotesChanged}} @isLoading={{@isLoading}} />
31
31
  <Order::Details::IntegratedVendorDetails @resource={{@resource}} @isLoading={{@isLoading}} />
32
32
  <Order::Details::Route @resource={{@resource}} @onChange={{@onRouteChanged}} @isLoading={{@isLoading}} />
@@ -38,4 +38,4 @@
38
38
  <Order::Details::Comments @resource={{@resource}} @onChange={{@onCommentsChanged}} @isLoading={{@isLoading}} />
39
39
  <Order::Details::Metadata @resource={{@resource}} @onChange={{@onMetadataChanged}} @isLoading={{@isLoading}} />
40
40
  {{/if}}
41
- </div>
41
+ </div>
@@ -1,3 +1,21 @@
1
1
  import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+ import { tracked } from '@glimmer/tracking';
2
4
 
3
- export default class OrderDetailsComponent extends Component {}
5
+ export default class OrderDetailsComponent extends Component {
6
+ @tracked proofReloadToken = 0;
7
+
8
+ get effectiveProofReloadToken() {
9
+ return `${this.args.proofReloadToken ?? 0}:${this.proofReloadToken}`;
10
+ }
11
+
12
+ @action handleActivityChanged(optionsOrActivity) {
13
+ if (typeof this.args.onActivityChanged === 'function') {
14
+ this.args.onActivityChanged(optionsOrActivity?.activityCreated ?? optionsOrActivity);
15
+ }
16
+
17
+ if (optionsOrActivity?.proofCreated) {
18
+ this.proofReloadToken++;
19
+ }
20
+ }
21
+ }
@@ -10,7 +10,7 @@
10
10
  <Badge @status={{@order.status}} @helpText={{if (eq @order.status (t "order.header.dispatched")) (concat (t "order.header.dispatched-at") @order.dispatchedAt)}} />
11
11
  {{#if @order.shouldDisplayDispatchLabel}}
12
12
  <Badge @status="info" class="ml-2" @helpText={{t "order.header.dispatched" htmlSafe=true order=@order.dispatchedAt}}>{{t
13
- "fleet-ops.component.order.header.dispatched"
13
+ "order-status.dispatched"
14
14
  }}</Badge>
15
15
  {{/if}}
16
16
  </div>
@@ -20,9 +20,6 @@
20
20
  {{#if this.firstStop.etaSeconds}}
21
21
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration this.firstStop.etaSeconds}}</Badge>
22
22
  {{/if}}
23
- {{#if this.firstStop.etaAt}}
24
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns this.firstStop.etaAt "d MMM HH:mm"}}</Badge>
25
- {{/if}}
26
23
  </div>
27
24
  <DisplayPlace @place={{this.firstStop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{this.firstStop}} @hideBadges={{true}} />
28
25
  </div>
@@ -58,9 +55,6 @@
58
55
  {{#if stop.etaSeconds}}
59
56
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration stop.etaSeconds}}</Badge>
60
57
  {{/if}}
61
- {{#if stop.etaAt}}
62
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns stop.etaAt "d MMM HH:mm"}}</Badge>
63
- {{/if}}
64
58
  </div>
65
59
  <DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{stop}} @hideBadges={{true}} />
66
60
  </div>
@@ -86,9 +80,6 @@
86
80
  {{#if stop.etaSeconds}}
87
81
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration stop.etaSeconds}}</Badge>
88
82
  {{/if}}
89
- {{#if stop.etaAt}}
90
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns stop.etaAt "d MMM HH:mm"}}</Badge>
91
- {{/if}}
92
83
  </div>
93
84
  <DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{stop}} @hideBadges={{true}} />
94
85
  </div>
@@ -117,9 +108,6 @@
117
108
  {{#if this.lastStop.etaSeconds}}
118
109
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration this.lastStop.etaSeconds}}</Badge>
119
110
  {{/if}}
120
- {{#if this.lastStop.etaAt}}
121
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns this.lastStop.etaAt "d MMM HH:mm"}}</Badge>
122
- {{/if}}
123
111
  </div>
124
112
  <DisplayPlace @place={{this.lastStop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{this.lastStop}} @hideBadges={{true}} />
125
113
  </div>
@@ -199,7 +199,7 @@
199
199
  <div class="field-info-container">
200
200
  <div class="field-name">Algorithm Code</div>
201
201
  <div class="field-value">
202
- <pre class="bg-gray-100 dark:bg-gray-900 font-mono p-3 rounded text-xs overflow-x-auto"><code class="font-mono text-xs">{{n-a @resource.algorithm}}</code></pre>
202
+ <pre class="bg-gray-100 dark:bg-gray-900 font-mono p-3 rounded text-xs overflow-x-auto"><code class="font-mono text-xs whitespace-break-spaces">{{n-a @resource.algorithm}}</code></pre>
203
203
  </div>
204
204
  </div>
205
205
  </ContentPanel>
@@ -10,7 +10,7 @@
10
10
  @disabled={{cannot-write @resource}}
11
11
  />
12
12
 
13
- <InputGroup @name={{t "service-rate.fields.service-order-label"}} @helpText={{t "service-rate.fields.service-order-help-text"}}>
13
+ <InputGroup @name={{t "service-rate.fields.service-order-label"}} @helpText={{t "service-rate.fields.service-order-help-text"}} @required={{true}}>
14
14
  <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
15
15
  <PowerSelect
16
16
  @options={{this.orderConfigActions.allOrderConfigs}}
@@ -53,6 +53,7 @@ export default class ServiceRateFormComponent extends Component {
53
53
  }
54
54
 
55
55
  @action selectRuleGeographyType(rule, { value }) {
56
+ rule.set('selected_geography_type', value);
56
57
  rule.set('zone', null);
57
58
  rule.set('zone_uuid', null);
58
59
  rule.set('service_area', null);
@@ -61,6 +62,7 @@ export default class ServiceRateFormComponent extends Component {
61
62
  }
62
63
 
63
64
  @action selectRuleServiceArea(rule, serviceArea) {
65
+ rule.set('selected_geography_type', 'service_area');
64
66
  rule.set('service_area', serviceArea);
65
67
  rule.set('service_area_uuid', serviceArea?.id);
66
68
  rule.set('zone', null);
@@ -69,6 +71,7 @@ export default class ServiceRateFormComponent extends Component {
69
71
  }
70
72
 
71
73
  @action selectRuleZone(rule, zone) {
74
+ rule.set('selected_geography_type', 'zone');
72
75
  rule.set('zone', zone);
73
76
  rule.set('zone_uuid', zone?.id);
74
77
  rule.set('service_area', null);
@@ -79,6 +82,7 @@ export default class ServiceRateFormComponent extends Component {
79
82
  @action setRuleFallback(rule, isFallback) {
80
83
  rule.set('is_fallback', Boolean(isFallback));
81
84
  if (isFallback) {
85
+ rule.set('selected_geography_type', 'fallback');
82
86
  rule.set('zone', null);
83
87
  rule.set('zone_uuid', null);
84
88
  rule.set('service_area', null);