@fleetbase/fleetops-engine 0.6.25 → 0.6.26

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 (48) hide show
  1. package/DRIVER_SCHEDULING.md +186 -0
  2. package/addon/components/driver/schedule.hbs +100 -0
  3. package/addon/components/driver/schedule.js +267 -0
  4. package/addon/components/order/kanban-card.hbs +2 -2
  5. package/addon/components/vehicle/details.hbs +594 -4
  6. package/addon/components/vehicle/form.hbs +467 -41
  7. package/addon/controllers/analytics/reports/index.js +3 -2
  8. package/addon/controllers/connectivity/devices/index.js +3 -3
  9. package/addon/controllers/connectivity/events/index.js +3 -2
  10. package/addon/controllers/connectivity/sensors/index.js +3 -5
  11. package/addon/controllers/connectivity/telematics/index.js +3 -1
  12. package/addon/controllers/maintenance/equipment/index.js +4 -4
  13. package/addon/controllers/maintenance/parts/index.js +4 -4
  14. package/addon/controllers/maintenance/work-orders/index.js +4 -4
  15. package/addon/controllers/management/contacts/customers.js +12 -10
  16. package/addon/controllers/management/contacts/index.js +3 -10
  17. package/addon/controllers/management/drivers/index/details.js +26 -13
  18. package/addon/controllers/management/drivers/index.js +4 -16
  19. package/addon/controllers/management/fleets/index.js +3 -13
  20. package/addon/controllers/management/fuel-reports/index.js +3 -10
  21. package/addon/controllers/management/issues/index.js +3 -12
  22. package/addon/controllers/management/places/index.js +4 -12
  23. package/addon/controllers/management/vehicles/index.js +3 -13
  24. package/addon/controllers/management/vendors/index.js +3 -13
  25. package/addon/controllers/operations/orders/index.js +5 -22
  26. package/addon/controllers/operations/scheduler/index.js +195 -6
  27. package/addon/controllers/operations/service-rates/index.js +34 -34
  28. package/addon/controllers/settings/payments/index.js +0 -6
  29. package/addon/routes.js +1 -0
  30. package/addon/services/driver-scheduling.js +171 -0
  31. package/addon/services/leaflet-layer-visibility-manager.js +4 -1
  32. package/addon/services/service-rate-actions.js +5 -1
  33. package/addon/templates/management/drivers/index/details/positions.hbs +1 -2
  34. package/addon/templates/management/drivers/index/details/schedule.hbs +1 -2
  35. package/addon/templates/operations/scheduler/index.hbs +48 -10
  36. package/addon/utils/fleet-ops-options.js +86 -0
  37. package/app/services/driver-scheduling.js +1 -0
  38. package/composer.json +1 -1
  39. package/extension.json +1 -1
  40. package/package.json +3 -3
  41. package/server/migrations/2025_11_17_033648_add_additional_columns_to_vehicles_table.php +142 -0
  42. package/server/src/Constraints/HOSConstraint.php +244 -0
  43. package/server/src/Http/Controllers/Api/v1/OrderController.php +1 -1
  44. package/server/src/Http/Controllers/Internal/v1/OrderController.php +8 -3
  45. package/server/src/Http/Resources/v1/Vehicle.php +197 -19
  46. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +211 -28
  47. package/server/src/Models/Driver.php +12 -8
  48. package/server/src/Models/Vehicle.php +101 -15
@@ -1,21 +1,105 @@
1
1
  import Controller from '@ember/controller';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { inject as service } from '@ember/service';
4
- import { action } from '@ember/object';
4
+ import { action, computed } from '@ember/object';
5
+ import { later } from '@ember/runloop';
6
+ import { task } from 'ember-concurrency';
5
7
  import { format, isValid as isValidDate } from 'date-fns';
6
8
  import isObject from '@fleetbase/ember-core/utils/is-object';
7
9
  import isJson from '@fleetbase/ember-core/utils/is-json';
8
10
  import createFullCalendarEventFromOrder, { createOrderEventTitle } from '../../../utils/create-full-calendar-event-from-order';
9
11
 
12
+ function createFullCalendarEventFromScheduleItem(item, driver) {
13
+ return {
14
+ id: item.id,
15
+ resourceId: driver.id,
16
+ title: `${driver.name} - Shift`,
17
+ start: item.start_at,
18
+ end: item.end_at,
19
+ backgroundColor: getScheduleItemColor(item),
20
+ extendedProps: {
21
+ scheduleItem: item,
22
+ driver: driver,
23
+ },
24
+ };
25
+ }
26
+
27
+ function getScheduleItemColor(item) {
28
+ const statusColors = {
29
+ pending: '#FFA500',
30
+ confirmed: '#4CAF50',
31
+ in_progress: '#2196F3',
32
+ completed: '#9E9E9E',
33
+ cancelled: '#F44336',
34
+ no_show: '#FF5722',
35
+ };
36
+ return statusColors[item.status] || '#4CAF50';
37
+ }
38
+
10
39
  export default class OperationsSchedulerIndexController extends Controller {
11
40
  @service modalsManager;
12
41
  @service notifications;
13
42
  @service store;
14
43
  @service intl;
15
44
  @service hostRouter;
45
+ @service scheduling;
16
46
  @tracked scheduledOrders = [];
17
47
  @tracked unscheduledOrders = [];
18
- @tracked events = [];
48
+ @tracked drivers = [];
49
+ @tracked scheduleItems = [];
50
+ @tracked viewMode = 'orders'; // 'orders' or 'drivers'
51
+
52
+ @computed('drivers', 'scheduleItems.[]', 'scheduledOrders.[]', 'viewMode') get events() {
53
+ if (this.viewMode === 'drivers') {
54
+ return this.scheduleItems.map((item) => {
55
+ const driver = this.drivers.find((d) => d.id === item.assignee_uuid);
56
+ return createFullCalendarEventFromScheduleItem(item, driver);
57
+ });
58
+ }
59
+ return this.scheduledOrders.map(createFullCalendarEventFromOrder);
60
+ }
61
+
62
+ @computed('drivers.[]') get calendarResources() {
63
+ return this.drivers.map((driver) => ({
64
+ id: driver.id,
65
+ title: driver.name,
66
+ extendedProps: { driver },
67
+ }));
68
+ }
69
+
70
+ get calendarStartDate() {
71
+ const now = new Date();
72
+ const dayOfWeek = now.getDay();
73
+ const diff = now.getDate() - dayOfWeek;
74
+ return new Date(now.setDate(diff)).toISOString();
75
+ }
76
+
77
+ get calendarEndDate() {
78
+ const now = new Date();
79
+ return new Date(now.setDate(now.getDate() + 28)).toISOString();
80
+ }
81
+
82
+ @task *loadDrivers() {
83
+ try {
84
+ const drivers = yield this.store.query('driver', { limit: 100 });
85
+ this.drivers = drivers.toArray();
86
+ } catch (error) {
87
+ this.notifications.serverError(error);
88
+ }
89
+ }
90
+
91
+ @task *loadScheduleItems() {
92
+ try {
93
+ const items = yield this.store.query('schedule-item', {
94
+ assignee_type: 'driver',
95
+ start_at_after: this.calendarStartDate,
96
+ end_at_before: this.calendarEndDate,
97
+ });
98
+ this.scheduleItems = items.toArray();
99
+ } catch (error) {
100
+ this.notifications.serverError(error);
101
+ }
102
+ }
19
103
 
20
104
  @action setCalendarApi(calendar) {
21
105
  this.calendar = calendar;
@@ -79,13 +163,70 @@ export default class OperationsSchedulerIndexController extends Controller {
79
163
  });
80
164
  }
81
165
 
166
+ @action async switchViewMode(mode) {
167
+ this.viewMode = mode;
168
+ if (mode === 'drivers') {
169
+ await this.loadDrivers.perform();
170
+ await this.loadScheduleItems.perform();
171
+ later(() => {
172
+ if (this.calendar) {
173
+ this.calendar.changeView('resourceTimelineWeek');
174
+ }
175
+ }, 100);
176
+ } else {
177
+ later(() => {
178
+ if (this.calendar) {
179
+ this.calendar.changeView('dayGridMonth');
180
+ }
181
+ }, 100);
182
+ }
183
+ }
184
+
82
185
  @action viewOrderAsEvent(eventClickInfo) {
83
186
  const { event } = eventClickInfo;
187
+ if (event.extendedProps && event.extendedProps.scheduleItem) {
188
+ return this.viewScheduleItem(event.extendedProps.scheduleItem, event.extendedProps.driver);
189
+ }
84
190
  const order = this.store.peekRecord('order', event.id);
85
-
86
191
  this.viewEvent(order, eventClickInfo);
87
192
  }
88
193
 
194
+ @action viewScheduleItem(scheduleItem, driver) {
195
+ this.modalsManager.show('modals/driver-shift', {
196
+ title: `${driver.name} - Shift Details`,
197
+ acceptButtonText: 'Save Changes',
198
+ acceptButtonIcon: 'save',
199
+ scheduleItem,
200
+ driver,
201
+ confirm: async (modal) => {
202
+ modal.startLoading();
203
+ try {
204
+ await scheduleItem.save();
205
+ this.notifications.success('Shift updated successfully');
206
+ await this.loadScheduleItems.perform();
207
+ modal.done();
208
+ } catch (error) {
209
+ this.notifications.serverError(error);
210
+ modal.stopLoading();
211
+ }
212
+ },
213
+ delete: async (modal) => {
214
+ if (confirm('Are you sure you want to delete this shift?')) {
215
+ modal.startLoading();
216
+ try {
217
+ await scheduleItem.destroyRecord();
218
+ this.notifications.success('Shift deleted successfully');
219
+ await this.loadScheduleItems.perform();
220
+ modal.done();
221
+ } catch (error) {
222
+ this.notifications.serverError(error);
223
+ modal.stopLoading();
224
+ }
225
+ }
226
+ },
227
+ });
228
+ }
229
+
89
230
  @action async scheduleEventFromDrop(dropInfo) {
90
231
  const { draggedEl, date } = dropInfo;
91
232
  const { dataset } = draggedEl;
@@ -112,17 +253,35 @@ export default class OperationsSchedulerIndexController extends Controller {
112
253
 
113
254
  @action async rescheduleEventFromDrag(eventDropInfo) {
114
255
  const { event } = eventDropInfo;
115
- const { start } = event;
256
+ const { start, end } = event;
257
+
258
+ if (event.extendedProps && event.extendedProps.scheduleItem) {
259
+ const scheduleItem = event.extendedProps.scheduleItem;
260
+ const newResourceId = event.getResources()[0]?.id;
261
+ try {
262
+ scheduleItem.set('start_at', start);
263
+ scheduleItem.set('end_at', end || start);
264
+ if (newResourceId && newResourceId !== scheduleItem.assignee_uuid) {
265
+ scheduleItem.set('assignee_uuid', newResourceId);
266
+ }
267
+ await scheduleItem.save();
268
+ this.notifications.success('Shift rescheduled successfully');
269
+ await this.loadScheduleItems.perform();
270
+ } catch (error) {
271
+ this.notifications.serverError(error);
272
+ eventDropInfo.revert();
273
+ }
274
+ return;
275
+ }
276
+
116
277
  const order = this.store.peekRecord('order', event.id);
117
278
  const scheduledTime = order.scheduledAtTime;
118
279
  const newDate = new Date(`${format(start, 'PP')} ${scheduledTime}`);
119
280
 
120
281
  try {
121
- // set and save order props
122
282
  order.set('scheduled_at', isValidDate(newDate) ? newDate : start);
123
283
  await order.save();
124
284
  this.setEventProperty(event, 'title', createOrderEventTitle(order));
125
-
126
285
  return this.hostRouter.refresh();
127
286
  } catch (error) {
128
287
  this.notifications.serverError(error);
@@ -130,6 +289,36 @@ export default class OperationsSchedulerIndexController extends Controller {
130
289
  }
131
290
  }
132
291
 
292
+ @action async addDriverShift() {
293
+ this.modalsManager.show('modals/add-driver-shift', {
294
+ title: 'Add Driver Shift',
295
+ acceptButtonText: 'Create Shift',
296
+ acceptButtonIcon: 'plus',
297
+ drivers: this.drivers,
298
+ confirm: async (modal) => {
299
+ modal.startLoading();
300
+ const { driver, startAt, endAt, duration } = modal.getOptions();
301
+ try {
302
+ const scheduleItem = this.store.createRecord('schedule-item', {
303
+ assignee_type: 'driver',
304
+ assignee_uuid: driver.id,
305
+ start_at: startAt,
306
+ end_at: endAt,
307
+ duration: duration,
308
+ status: 'pending',
309
+ });
310
+ await scheduleItem.save();
311
+ this.notifications.success('Shift created successfully');
312
+ await this.loadScheduleItems.perform();
313
+ modal.done();
314
+ } catch (error) {
315
+ this.notifications.serverError(error);
316
+ modal.stopLoading();
317
+ }
318
+ },
319
+ });
320
+ }
321
+
133
322
  removeEvent(event) {
134
323
  if (isObject(event) && typeof event.remove === 'function') {
135
324
  event.remove();
@@ -14,43 +14,47 @@ export default class OperationsServiceRatesIndexController extends Controller {
14
14
  @tracked sort = '-created_at';
15
15
 
16
16
  /** action buttons */
17
- @tracked actionButtons = [
18
- {
19
- icon: 'refresh',
20
- onClick: this.serviceRateActions.refresh,
21
- helpText: this.intl.t('common.refresh'),
22
- },
23
- {
24
- text: this.intl.t('common.new'),
25
- type: 'primary',
26
- icon: 'plus',
27
- onClick: this.serviceRateActions.transition.create,
28
- },
29
- {
30
- text: this.intl.t('common.export'),
31
- icon: 'long-arrow-up',
32
- iconClass: 'rotate-icon-45',
33
- wrapperClass: 'hidden md:flex',
34
- onClick: this.serviceRateActions.export,
35
- },
36
- ];
17
+ get actionButtons() {
18
+ return [
19
+ {
20
+ icon: 'refresh',
21
+ onClick: this.serviceRateActions.refresh,
22
+ helpText: this.intl.t('common.refresh'),
23
+ },
24
+ {
25
+ text: this.intl.t('common.new'),
26
+ type: 'primary',
27
+ icon: 'plus',
28
+ onClick: this.serviceRateActions.transition.create,
29
+ },
30
+ {
31
+ text: this.intl.t('common.export'),
32
+ icon: 'long-arrow-up',
33
+ iconClass: 'rotate-icon-45',
34
+ wrapperClass: 'hidden md:flex',
35
+ onClick: this.serviceRateActions.export,
36
+ },
37
+ ];
38
+ }
37
39
 
38
40
  /** bulk action buttons */
39
- @tracked bulkActions = [
40
- {
41
- label: 'Delete selected...',
42
- class: 'text-red-500',
43
- fn: this.serviceRateActions.bulkDelete,
44
- },
45
- ];
41
+ get bulkActions() {
42
+ return [
43
+ {
44
+ label: 'Delete selected...',
45
+ class: 'text-red-500',
46
+ fn: this.serviceRateActions.bulkDelete,
47
+ },
48
+ ];
49
+ }
46
50
 
47
51
  /** columns **/
48
52
  get columns() {
49
53
  return [
50
54
  {
55
+ sticky: true,
51
56
  label: this.intl.t('column.id'),
52
57
  valuePath: 'public_id',
53
- width: '150px',
54
58
  cellComponent: 'table/cell/anchor',
55
59
  permission: 'fleet-ops view service-rate',
56
60
  onClick: this.serviceRateActions.transition.view,
@@ -63,7 +67,6 @@ export default class OperationsServiceRatesIndexController extends Controller {
63
67
  label: this.intl.t('column.service'),
64
68
  valuePath: 'service_name',
65
69
  cellComponent: 'table/cell/base',
66
- width: '125px',
67
70
  resizable: true,
68
71
  sortable: true,
69
72
  filterable: false,
@@ -72,7 +75,6 @@ export default class OperationsServiceRatesIndexController extends Controller {
72
75
  label: this.intl.t('column.service-area'),
73
76
  valuePath: 'service_area.name',
74
77
  cellComponent: 'table/cell/base',
75
- width: '125px',
76
78
  resizable: true,
77
79
  sortable: true,
78
80
  filterable: true,
@@ -85,7 +87,6 @@ export default class OperationsServiceRatesIndexController extends Controller {
85
87
  label: this.intl.t('column.zone'),
86
88
  valuePath: 'zone.name',
87
89
  cellComponent: 'table/cell/base',
88
- width: '125px',
89
90
  resizable: true,
90
91
  sortable: true,
91
92
  filterable: true,
@@ -98,7 +99,6 @@ export default class OperationsServiceRatesIndexController extends Controller {
98
99
  label: this.intl.t('column.created-at'),
99
100
  valuePath: 'createdAt',
100
101
  sortParam: 'created_at',
101
- width: '125px',
102
102
  resizable: true,
103
103
  sortable: true,
104
104
  filterable: true,
@@ -108,7 +108,6 @@ export default class OperationsServiceRatesIndexController extends Controller {
108
108
  label: this.intl.t('column.updated-at'),
109
109
  valuePath: 'updatedAt',
110
110
  sortParam: 'updated_at',
111
- width: '125px',
112
111
  resizable: true,
113
112
  sortable: true,
114
113
  hidden: true,
@@ -123,7 +122,8 @@ export default class OperationsServiceRatesIndexController extends Controller {
123
122
  ddButtonIconPrefix: 'fas',
124
123
  cellClassNames: 'overflow-visible',
125
124
  wrapperClass: 'flex items-center justify-end mx-2',
126
- width: '10%',
125
+ sticky: 'right',
126
+ width: 60,
127
127
  actions: [
128
128
  {
129
129
  label: this.intl.t('column.edit-service'),
@@ -19,35 +19,29 @@ export default class SettingsPaymentsIndexController extends Controller {
19
19
  label: 'Purchase Rate ID',
20
20
  valuePath: 'public_id',
21
21
  cellComponent: 'click-to-copy',
22
- width: '20%',
23
22
  },
24
23
  {
25
24
  label: 'Service Quote',
26
25
  valuePath: 'service_quote_id',
27
26
  cellComponent: 'click-to-copy',
28
- width: '20%',
29
27
  },
30
28
  {
31
29
  label: 'Order',
32
30
  valuePath: 'order_id',
33
31
  cellComponent: 'click-to-copy',
34
- width: '20%',
35
32
  },
36
33
  {
37
34
  label: 'Customer',
38
35
  valuePath: 'customer.name',
39
- width: '20%',
40
36
  },
41
37
  {
42
38
  label: 'Amount',
43
39
  valuePath: 'amount',
44
40
  cellComponent: 'table/cell/currency',
45
- width: '20%',
46
41
  },
47
42
  {
48
43
  label: 'Date',
49
44
  valuePath: 'created_at',
50
- width: '20%',
51
45
  },
52
46
  ];
53
47
 
package/addon/routes.js CHANGED
@@ -63,6 +63,7 @@ export default buildRoutes(function () {
63
63
  this.route('details', { path: '/:public_id' }, function () {
64
64
  this.route('index', { path: '/' });
65
65
  this.route('positions');
66
+ this.route('schedule');
66
67
  });
67
68
  this.route('edit', { path: '/edit/:public_id' });
68
69
  });
@@ -0,0 +1,171 @@
1
+ import Service, { inject as service } from '@ember/service';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { task } from 'ember-concurrency';
4
+
5
+ export default class DriverSchedulingService extends Service {
6
+ @service store;
7
+ @service fetch;
8
+ @service notifications;
9
+ @tracked currentSchedule = null;
10
+ @tracked scheduleItems = [];
11
+ @tracked constraints = [];
12
+
13
+ /**
14
+ * Load a schedule by ID
15
+ */
16
+ @task *loadSchedule(scheduleId) {
17
+ try {
18
+ const schedule = yield this.store.findRecord('schedule', scheduleId, {
19
+ include: 'items',
20
+ reload: true,
21
+ });
22
+ this.currentSchedule = schedule;
23
+ return schedule;
24
+ } catch (error) {
25
+ this.notifications.serverError(error);
26
+ throw error;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Create a new schedule
32
+ */
33
+ @task *createSchedule(data) {
34
+ try {
35
+ const schedule = this.store.createRecord('schedule', data);
36
+ yield schedule.save();
37
+ this.notifications.success('Schedule created successfully');
38
+ return schedule;
39
+ } catch (error) {
40
+ this.notifications.serverError(error);
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Create a new schedule item
47
+ */
48
+ @task *createScheduleItem(data) {
49
+ try {
50
+ const item = this.store.createRecord('schedule-item', data);
51
+ yield item.save();
52
+ this.notifications.success('Schedule item created successfully');
53
+ return item;
54
+ } catch (error) {
55
+ this.notifications.serverError(error);
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Update a schedule item
62
+ */
63
+ @task *updateScheduleItem(item, data) {
64
+ try {
65
+ item.setProperties(data);
66
+ yield item.save();
67
+ this.notifications.success('Schedule item updated successfully');
68
+ return item;
69
+ } catch (error) {
70
+ this.notifications.serverError(error);
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Delete a schedule item
77
+ */
78
+ @task *deleteScheduleItem(item) {
79
+ try {
80
+ yield item.destroyRecord();
81
+ this.notifications.success('Schedule item deleted successfully');
82
+ } catch (error) {
83
+ this.notifications.serverError(error);
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get schedule items for an assignee
90
+ */
91
+ @task *getScheduleItemsForAssignee(assigneeType, assigneeUuid, filters = {}) {
92
+ try {
93
+ const items = yield this.store.query('schedule-item', {
94
+ assignee_type: assigneeType,
95
+ assignee_uuid: assigneeUuid,
96
+ ...filters,
97
+ });
98
+ this.scheduleItems = items.toArray();
99
+ return items;
100
+ } catch (error) {
101
+ this.notifications.serverError(error);
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Check availability for a subject
108
+ */
109
+ @task *checkAvailability(subjectType, subjectUuid, startAt, endAt) {
110
+ try {
111
+ const response = yield this.fetch.get('schedule-availability/check', {
112
+ subject_type: subjectType,
113
+ subject_uuid: subjectUuid,
114
+ start_at: startAt,
115
+ end_at: endAt,
116
+ });
117
+ return response.available;
118
+ } catch (error) {
119
+ this.notifications.serverError(error);
120
+ return false;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Set availability for a subject
126
+ */
127
+ @task *setAvailability(data) {
128
+ try {
129
+ const availability = this.store.createRecord('schedule-availability', data);
130
+ yield availability.save();
131
+ this.notifications.success('Availability set successfully');
132
+ return availability;
133
+ } catch (error) {
134
+ this.notifications.serverError(error);
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Load constraints for a subject
141
+ */
142
+ @task *loadConstraints(subjectType, subjectUuid) {
143
+ try {
144
+ const constraints = yield this.store.query('schedule-constraint', {
145
+ subject_type: subjectType,
146
+ subject_uuid: subjectUuid,
147
+ is_active: true,
148
+ });
149
+ this.constraints = constraints.toArray();
150
+ return constraints;
151
+ } catch (error) {
152
+ this.notifications.serverError(error);
153
+ throw error;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Validate a schedule item against constraints
159
+ */
160
+ @task *validateScheduleItem(item) {
161
+ try {
162
+ const response = yield this.fetch.post('schedule-items/validate', {
163
+ schedule_item: item.serialize(),
164
+ });
165
+ return response.violations || [];
166
+ } catch (error) {
167
+ this.notifications.serverError(error);
168
+ return [];
169
+ }
170
+ }
171
+ }
@@ -1,5 +1,6 @@
1
1
  import Service, { inject as service } from '@ember/service';
2
2
  import { next } from '@ember/runloop';
3
+ import { isNone } from '@ember/utils';
3
4
 
4
5
  const L = window.L || window.leaflet;
5
6
  export default class LeafletLayerVisibilityService extends Service {
@@ -203,7 +204,9 @@ export default class LeafletLayerVisibilityService extends Service {
203
204
  if (layer.setStyle) {
204
205
  const base = { opacity: 1 };
205
206
  // leaflets default fillOpacity if fill:true is ~0.2
206
- base.fillOpacity = layer.options?.fill ? (layer.options?.fillOpacity ?? 0.2) : 0;
207
+ const fillOpacity = layer.options?.fillOpacity ?? 0.2;
208
+ const hasFillOpacity = !isNone(layer.options?.fill);
209
+ base.fillOpacity = hasFillOpacity ? fillOpacity : 0;
207
210
  layer.setStyle(base);
208
211
  } else if (layer.setOpacity) {
209
212
  layer.setOpacity(1);
@@ -1,5 +1,6 @@
1
1
  import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
2
2
  import { task } from 'ember-concurrency';
3
+ import { isNone } from '@ember/utils';
3
4
  import serializePayload from '../utils/serialize-payload';
4
5
 
5
6
  export default class ServiceRateActionsService extends ResourceActionService {
@@ -115,12 +116,15 @@ export default class ServiceRateActionsService extends ResourceActionService {
115
116
  @task *getServiceQuotes(serviceRate, order) {
116
117
  if (order.payload.payloadCoordinates.length < 2) return;
117
118
 
119
+ const hasFacilitator = !isNone(order.facilitator);
120
+ const facilitatorServiceType = order.facilitator?.get('service_types.firstObject.key') ?? order.type;
121
+
118
122
  try {
119
123
  const serviceQuotes = yield this.fetch.post('service-quotes/preliminary', {
120
124
  payload: serializePayload(order.payload),
121
125
  distance: order.route.summary?.totalDistance,
122
126
  time: order.route.summary?.totalTime,
123
- service_type: order.facilitator ? (this.args.facilitator.get('service_types.firstObject.key') ?? order.type) : order.type,
127
+ service_type: hasFacilitator ? facilitatorServiceType : order.type,
124
128
  facilitator: order.facilitator?.public_id,
125
129
  scheduled_at: order.scheduled_at,
126
130
  is_route_optimized: order.optimized,
@@ -1,2 +1 @@
1
- {{page-title "Positions"}}
2
- {{outlet}}
1
+ <PositionsReplay @resource={{@model}} />
@@ -1,2 +1 @@
1
-
2
- {{outlet}}
1
+ <Driver::Schedule @resource={{@model}} />