@fleetbase/fleetops-engine 0.6.48 → 0.6.50
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.
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/form.hbs +2 -2
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +2 -2
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/details.hbs +5 -1
- package/addon/components/driver/form.hbs +11 -1
- package/addon/components/fleet-listing-panel.hbs +2 -2
- package/addon/components/fleet-listing-panel.js +10 -0
- package/addon/components/map/google-live-map.js +18 -75
- package/addon/components/map/leaflet-live-map.hbs +187 -37
- package/addon/components/map/leaflet-live-map.js +184 -20
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +45 -3
- package/addon/components/modals/update-order-activity.js +143 -5
- package/addon/components/orchestrator/phase-builder.hbs +21 -0
- package/addon/components/orchestrator/phase-builder.js +13 -0
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/orchestrator-workbench.js +1 -0
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +11 -0
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order/form.hbs +2 -2
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -17
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +41 -0
- package/addon/components/service-rate/form.hbs +118 -0
- package/addon/components/service-rate/form.js +55 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
- package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
- package/addon/components/widget/fuel-efficiency.hbs +26 -0
- package/addon/components/widget/fuel-efficiency.js +43 -0
- package/addon/components/widget/geofence-violations.hbs +44 -0
- package/addon/components/widget/geofence-violations.js +55 -0
- package/addon/components/widget/issues-insights.hbs +43 -0
- package/addon/components/widget/issues-insights.js +62 -0
- package/addon/components/widget/kpi-active-orders.hbs +10 -0
- package/addon/components/widget/kpi-active-orders.js +5 -0
- package/addon/components/widget/kpi-aov.hbs +10 -0
- package/addon/components/widget/kpi-aov.js +5 -0
- package/addon/components/widget/kpi-distance.hbs +10 -0
- package/addon/components/widget/kpi-distance.js +5 -0
- package/addon/components/widget/kpi-drivers-online.hbs +10 -0
- package/addon/components/widget/kpi-drivers-online.js +5 -0
- package/addon/components/widget/kpi-earnings.hbs +10 -0
- package/addon/components/widget/kpi-earnings.js +5 -0
- package/addon/components/widget/kpi-open-issues.hbs +11 -0
- package/addon/components/widget/kpi-open-issues.js +5 -0
- package/addon/components/widget/kpi-tile.hbs +59 -0
- package/addon/components/widget/kpi-tile.js +168 -0
- package/addon/components/widget/live-fleet.hbs +122 -0
- package/addon/components/widget/live-fleet.js +365 -0
- package/addon/components/widget/maintenance-overview.hbs +45 -0
- package/addon/components/widget/maintenance-overview.js +40 -0
- package/addon/components/widget/on-time-delivery.hbs +27 -0
- package/addon/components/widget/on-time-delivery.js +73 -0
- package/addon/components/widget/operations-pulse.hbs +51 -0
- package/addon/components/widget/operations-pulse.js +173 -0
- package/addon/components/widget/orders-by-status.hbs +26 -0
- package/addon/components/widget/orders-by-status.js +54 -0
- package/addon/components/widget/revenue-trend.hbs +34 -0
- package/addon/components/widget/revenue-trend.js +67 -0
- package/addon/components/widget/top-drivers.hbs +44 -0
- package/addon/components/widget/top-drivers.js +54 -0
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/operations/orders/index/details.js +4 -4
- package/addon/controllers/operations/orders/index.js +14 -0
- package/addon/controllers/settings/routing.js +26 -0
- package/addon/extension.js +172 -3
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/map-adapter/google.js +13 -2
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +205 -88
- package/addon/templates/maintenance/equipment/index.hbs +1 -1
- package/addon/templates/maintenance/parts/index.hbs +1 -1
- package/addon/templates/maintenance/schedules/index.hbs +1 -1
- package/addon/templates/management/vehicles/index.hbs +1 -1
- package/addon/templates/settings/avatars.hbs +1 -1
- package/addon/templates/settings/custom-fields.hbs +1 -1
- package/addon/templates/settings/map.hbs +1 -1
- package/addon/templates/settings/navigator-app.hbs +1 -1
- package/addon/templates/settings/notifications.hbs +1 -1
- package/addon/templates/settings/orchestrator.hbs +1 -1
- package/addon/templates/settings/payments/index.hbs +1 -1
- package/addon/templates/settings/payments/onboard.hbs +1 -1
- package/addon/templates/settings/routing.hbs +22 -2
- package/addon/templates/settings/scheduling.hbs +1 -1
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/live-map-card-content.js +171 -0
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/widget/fuel-efficiency.js +1 -0
- package/app/components/widget/geofence-violations.js +1 -0
- package/app/components/widget/issues-insights.js +1 -0
- package/app/components/widget/kpi-active-orders.js +1 -0
- package/app/components/widget/kpi-aov.js +1 -0
- package/app/components/widget/kpi-distance.js +1 -0
- package/app/components/widget/kpi-drivers-online.js +1 -0
- package/app/components/widget/kpi-earnings.js +1 -0
- package/app/components/widget/kpi-open-issues.js +1 -0
- package/app/components/widget/kpi-tile.js +1 -0
- package/app/components/widget/live-fleet.js +1 -0
- package/app/components/widget/maintenance-overview.js +1 -0
- package/app/components/widget/on-time-delivery.js +1 -0
- package/app/components/widget/operations-pulse.js +1 -0
- package/app/components/widget/orders-by-status.js +1 -0
- package/app/components/widget/revenue-trend.js +1 -0
- package/app/components/widget/top-drivers.js +1 -0
- package/app/utils/live-map-card-content.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +1 -0
- package/server/config/telematics.php +111 -1
- package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
- package/server/seeders/Testing/ConnectivitySeeder.php +213 -0
- package/server/seeders/Testing/FleetSeeder.php +341 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
- package/server/seeders/Testing/NetworkSeeder.php +250 -0
- package/server/seeders/Testing/OrdersSeeder.php +198 -0
- package/server/seeders/Testing/TestingSeeder.php +40 -0
- package/server/seeders/{VroomOrchestrationSeeder.php → Testing/VroomOrchestrationSeeder.php} +29 -22
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +441 -32
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +46 -1
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/Driver.php +2 -0
- package/server/src/Http/Resources/v1/Fleet.php +4 -0
- package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
- package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Models/Contact.php +92 -15
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +14 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Order.php +29 -4
- package/server/src/Models/OrderConfig.php +14 -11
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Notifications/LateDeparture.php +48 -0
- package/server/src/Notifications/ProlongedStoppage.php +48 -0
- package/server/src/Notifications/RouteDeviation.php +48 -0
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +29 -2
- package/server/src/Providers/FleetOpsServiceProvider.php +6 -0
- package/server/src/Providers/NotificationServiceProvider.php +3 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
- package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
- package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
- package/server/src/Support/Analytics/IssuesInsights.php +67 -0
- package/server/src/Support/Analytics/LiveFleet.php +127 -0
- package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
- package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
- package/server/src/Support/Analytics/OperationsPulse.php +86 -0
- package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
- package/server/src/Support/Analytics/RevenueTrend.php +78 -0
- package/server/src/Support/Analytics/TopDrivers.php +92 -0
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +170 -0
- package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
- package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
- package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
- package/server/src/Support/Metrics/EarningsMetric.php +30 -0
- package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
- package/server/src/Support/Metrics/MoneyMetric.php +30 -0
- package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
- package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
- package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
- package/server/src/Support/Metrics/Registry.php +46 -0
- package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
- package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
- package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
- package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
- package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
- package/server/src/Support/Metrics.php +46 -249
- package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +50 -0
- package/server/tests/AnalyticsRoutesTest.php +41 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +84 -0
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/server/tests/VroomOrchestrationEngineTest.php +91 -0
- package/translations/en-us.yaml +38 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -8,11 +8,14 @@ import { guidFor } from '@ember/object/internals';
|
|
|
8
8
|
import { camelize, capitalize, dasherize } from '@ember/string';
|
|
9
9
|
import { singularize, pluralize } from 'ember-inflector';
|
|
10
10
|
import { all } from 'rsvp';
|
|
11
|
-
import { task } from 'ember-concurrency';
|
|
11
|
+
import { task, timeout } from 'ember-concurrency';
|
|
12
12
|
import { next } from '@ember/runloop';
|
|
13
13
|
import getModelName from '@fleetbase/ember-core/utils/get-model-name';
|
|
14
14
|
|
|
15
15
|
export default class MapLeafletLiveMapComponent extends Component {
|
|
16
|
+
liveViewportReloadDebounceMs = 300;
|
|
17
|
+
liveViewportResourceLimit = 500;
|
|
18
|
+
|
|
16
19
|
@service mapManager;
|
|
17
20
|
@service mapSettings;
|
|
18
21
|
@service leafletMapManager;
|
|
@@ -98,6 +101,11 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
98
101
|
this.universe.off('fleet-ops.geofence.exited', this._geofenceExitedHandler);
|
|
99
102
|
this._geofenceExitedHandler = null;
|
|
100
103
|
}
|
|
104
|
+
if (this._viewportReloadHandler && this.map && typeof this.map.off === 'function') {
|
|
105
|
+
this.map.off('moveend', this._viewportReloadHandler);
|
|
106
|
+
this.map.off('zoomend', this._viewportReloadHandler);
|
|
107
|
+
this._viewportReloadHandler = null;
|
|
108
|
+
}
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
@action didLoad({ target: map }) {
|
|
@@ -106,8 +114,9 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
106
114
|
this.trigger('onLoad', ...arguments);
|
|
107
115
|
this.load.perform();
|
|
108
116
|
// Listen for map move/zoom events to trigger viewport-based resource reload
|
|
109
|
-
|
|
110
|
-
map.on('
|
|
117
|
+
this._viewportReloadHandler = () => this.reloadResourcesInViewport.perform();
|
|
118
|
+
map.on('moveend', this._viewportReloadHandler);
|
|
119
|
+
map.on('zoomend', this._viewportReloadHandler);
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
/**
|
|
@@ -211,13 +220,27 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
211
220
|
this.#createZoneContextMenu(zone, layer);
|
|
212
221
|
}
|
|
213
222
|
|
|
223
|
+
@action rebuildMapContextMenu() {
|
|
224
|
+
if (!this.map) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.#createMapContextMenu(this.map);
|
|
229
|
+
this.#refreshResourceContextMenus();
|
|
230
|
+
this.#pruneResourceContextMenus();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@action syncServiceAreaContextMenus() {
|
|
234
|
+
this.rebuildMapContextMenu();
|
|
235
|
+
}
|
|
236
|
+
|
|
214
237
|
/** load resources and wait for stuff here and trigger map ready **/
|
|
215
238
|
@task *load() {
|
|
216
239
|
try {
|
|
217
240
|
// Get initial map bounds for spatial filtering (provider-agnostic)
|
|
218
241
|
const mapBounds = this.mapManager.getBounds?.() ?? (this.map ? this.map.getBounds() : null);
|
|
219
242
|
const bounds = this.#serializeBoundsForRequest(mapBounds);
|
|
220
|
-
const params = bounds
|
|
243
|
+
const params = this.#liveViewportRequestParams(bounds);
|
|
221
244
|
|
|
222
245
|
const data = yield all([
|
|
223
246
|
this.loadResource.perform('routes'),
|
|
@@ -242,12 +265,19 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
242
265
|
if (this.isViewportReloadSuspended) {
|
|
243
266
|
return;
|
|
244
267
|
}
|
|
268
|
+
|
|
269
|
+
yield timeout(this.liveViewportReloadDebounceMs);
|
|
270
|
+
|
|
271
|
+
if (this.isViewportReloadSuspended) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
245
275
|
// Get current map bounds (provider-agnostic)
|
|
246
276
|
const rawBounds = this.mapManager.getBounds?.() ?? this.map?.getBounds();
|
|
247
277
|
if (!rawBounds) return;
|
|
248
278
|
const bounds = this.#serializeBoundsForRequest(rawBounds);
|
|
249
279
|
if (!bounds) return;
|
|
250
|
-
const params =
|
|
280
|
+
const params = this.#liveViewportRequestParams(bounds);
|
|
251
281
|
|
|
252
282
|
// Reload spatially-filtered resources (drivers, vehicles, places)
|
|
253
283
|
// Orders, routes, and service-areas are not spatially filtered
|
|
@@ -445,6 +475,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
445
475
|
return null;
|
|
446
476
|
}
|
|
447
477
|
|
|
478
|
+
#liveViewportRequestParams(bounds) {
|
|
479
|
+
return bounds ? { bounds, limit: this.liveViewportResourceLimit } : { limit: this.liveViewportResourceLimit };
|
|
480
|
+
}
|
|
481
|
+
|
|
448
482
|
#setResourceLayer(model, layer, options = {}) {
|
|
449
483
|
const { hidden = false } = options;
|
|
450
484
|
const type = getModelName(model);
|
|
@@ -462,6 +496,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
462
496
|
}
|
|
463
497
|
|
|
464
498
|
#createMapContextMenu(map) {
|
|
499
|
+
const serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []);
|
|
465
500
|
const items = [
|
|
466
501
|
{
|
|
467
502
|
text: this.intl.t('live-map.show-coordinates'),
|
|
@@ -494,10 +529,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
494
529
|
callback: () => this.geofence.createServiceArea(),
|
|
495
530
|
index: 5,
|
|
496
531
|
},
|
|
497
|
-
|
|
498
|
-
...
|
|
532
|
+
serviceAreas.length ? { separator: true } : null,
|
|
533
|
+
...serviceAreas.map((serviceArea, i) => {
|
|
499
534
|
return {
|
|
500
|
-
text: this.intl.t('live-map.focus-service', { serviceName: serviceArea
|
|
535
|
+
text: this.intl.t('live-map.focus-service', { serviceName: this.#resourceDisplayName(serviceArea) }),
|
|
501
536
|
callback: () => this.geofence.focusServiceArea(serviceArea),
|
|
502
537
|
index: 6 + i,
|
|
503
538
|
};
|
|
@@ -511,71 +546,200 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
511
546
|
}
|
|
512
547
|
|
|
513
548
|
#createZoneContextMenu(zone, layer) {
|
|
549
|
+
const zoneName = this.#resourceDisplayName(zone);
|
|
514
550
|
let items = [
|
|
515
551
|
{
|
|
516
552
|
separator: true,
|
|
517
553
|
},
|
|
518
554
|
{
|
|
519
|
-
text: this.intl.t('live-map.edit-zone', { zoneName
|
|
555
|
+
text: this.intl.t('live-map.edit-zone', { zoneName }),
|
|
520
556
|
callback: () => this.zoneActions.modal.edit(zone),
|
|
521
557
|
},
|
|
522
558
|
{
|
|
523
|
-
text: this.intl.t('live-map.delete-zone', { zoneName
|
|
524
|
-
callback: () => this
|
|
559
|
+
text: this.intl.t('live-map.delete-zone', { zoneName }),
|
|
560
|
+
callback: () => this.#deleteZone(zone),
|
|
525
561
|
},
|
|
526
562
|
];
|
|
527
563
|
|
|
528
564
|
if (!this.mapManager.isGoogleMaps) {
|
|
529
565
|
items.splice(2, 0, {
|
|
530
|
-
text: this.intl.t('live-map.edit-boundaries', { resource:
|
|
566
|
+
text: this.intl.t('live-map.edit-boundaries', { resource: zoneName }),
|
|
531
567
|
callback: () => this.geofence.editZone(zone),
|
|
532
568
|
});
|
|
533
569
|
}
|
|
534
570
|
|
|
535
571
|
// create contextmenu registry
|
|
536
|
-
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(
|
|
572
|
+
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(this.#resourceContextKey('zone', zone), layer, items, { zone });
|
|
537
573
|
this.universe.trigger('fleet-ops:contextmenu:zone:created', contextmenuRegistry, this.leafletContextmenuManager);
|
|
538
574
|
|
|
539
575
|
return contextmenuRegistry;
|
|
540
576
|
}
|
|
541
577
|
|
|
542
578
|
#createServiceAreaContextMenu(serviceArea, layer) {
|
|
579
|
+
const serviceName = this.#resourceDisplayName(serviceArea);
|
|
543
580
|
let items = [
|
|
544
581
|
{
|
|
545
582
|
separator: true,
|
|
546
583
|
},
|
|
547
584
|
{
|
|
548
|
-
text: this.intl.t('live-map.blur-service', { serviceName
|
|
585
|
+
text: this.intl.t('live-map.blur-service', { serviceName }),
|
|
549
586
|
callback: () => this.geofence.blurServiceArea(serviceArea),
|
|
550
587
|
},
|
|
551
588
|
{
|
|
552
|
-
text: this.intl.t('live-map.create-zone', { serviceName
|
|
589
|
+
text: this.intl.t('live-map.create-zone', { serviceName }),
|
|
553
590
|
callback: () => this.geofence.createZone(serviceArea),
|
|
554
591
|
},
|
|
555
592
|
{
|
|
556
|
-
text: this.intl.t('live-map.edit-service', { serviceName
|
|
593
|
+
text: this.intl.t('live-map.edit-service', { serviceName }),
|
|
557
594
|
callback: () => this.serviceAreaActions.modal.edit(serviceArea),
|
|
558
595
|
},
|
|
559
596
|
{
|
|
560
|
-
text: this.intl.t('live-map.delete-service', { serviceName
|
|
561
|
-
callback: () => this
|
|
597
|
+
text: this.intl.t('live-map.delete-service', { serviceName }),
|
|
598
|
+
callback: () => this.#deleteServiceArea(serviceArea),
|
|
562
599
|
},
|
|
563
600
|
];
|
|
564
601
|
|
|
565
602
|
if (!this.mapManager.isGoogleMaps) {
|
|
566
603
|
items.splice(4, 0, {
|
|
567
|
-
text: this.intl.t('live-map.edit-boundaries', { resource:
|
|
604
|
+
text: this.intl.t('live-map.edit-boundaries', { resource: serviceName }),
|
|
568
605
|
callback: () => this.geofence.editServiceArea(serviceArea),
|
|
569
606
|
});
|
|
570
607
|
}
|
|
571
608
|
|
|
572
609
|
// create contextmenu registry
|
|
573
|
-
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(
|
|
610
|
+
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(this.#resourceContextKey('service-area', serviceArea), layer, items, { serviceArea });
|
|
574
611
|
this.universe.trigger('fleet-ops:contextmenu:service-area:created', contextmenuRegistry, this.leafletContextmenuManager);
|
|
575
612
|
|
|
576
613
|
return contextmenuRegistry;
|
|
577
614
|
}
|
|
578
615
|
|
|
616
|
+
#resourceContextKey(type, resource) {
|
|
617
|
+
return `${type}:${resource?.public_id ?? resource?.id}`;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#resourceDisplayName(resource) {
|
|
621
|
+
return resource?.get?.('name') || resource?.name || resource?.display_name || resource?.public_id || resource?.id || '';
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
#deleteServiceArea(serviceArea) {
|
|
625
|
+
const zones = Array.from(serviceArea?.zones ?? []);
|
|
626
|
+
|
|
627
|
+
return this.serviceAreaActions.delete(
|
|
628
|
+
serviceArea,
|
|
629
|
+
{},
|
|
630
|
+
{
|
|
631
|
+
callback: () => {
|
|
632
|
+
this.#removeServiceAreaFromMapState(serviceArea, zones);
|
|
633
|
+
this.rebuildMapContextMenu();
|
|
634
|
+
},
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
#deleteZone(zone) {
|
|
640
|
+
return this.zoneActions.delete(
|
|
641
|
+
zone,
|
|
642
|
+
{},
|
|
643
|
+
{
|
|
644
|
+
callback: () => {
|
|
645
|
+
this.#removeZoneFromMapState(zone);
|
|
646
|
+
this.rebuildMapContextMenu();
|
|
647
|
+
},
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
#removeServiceAreaFromMapState(serviceArea, zones = []) {
|
|
653
|
+
const serviceAreaId = serviceArea?.id;
|
|
654
|
+
const serviceAreaKey = this.#resourceContextKey('service-area', serviceArea);
|
|
655
|
+
|
|
656
|
+
zones.forEach((zone) => this.#removeZoneFromMapState(zone));
|
|
657
|
+
this.#removeOverlay(serviceArea);
|
|
658
|
+
this.leafletContextmenuManager.removeContextMenu(serviceAreaKey);
|
|
659
|
+
|
|
660
|
+
this.serviceAreaActions.serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []).filter((candidate) => candidate?.id !== serviceAreaId);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
#removeZoneFromMapState(zone) {
|
|
664
|
+
const zoneId = zone?.id;
|
|
665
|
+
const zoneKey = this.#resourceContextKey('zone', zone);
|
|
666
|
+
|
|
667
|
+
this.#removeOverlay(zone);
|
|
668
|
+
this.leafletContextmenuManager.removeContextMenu(zoneKey);
|
|
669
|
+
|
|
670
|
+
for (const serviceArea of Array.from(this.serviceAreaActions.serviceAreas ?? [])) {
|
|
671
|
+
const zones = serviceArea?.zones;
|
|
672
|
+
if (!zones) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const currentZones = Array.from(zones);
|
|
677
|
+
const nextZones = currentZones.filter((candidate) => candidate?.id !== zoneId);
|
|
678
|
+
if (nextZones.length !== currentZones.length) {
|
|
679
|
+
if (typeof zones.removeObject === 'function') {
|
|
680
|
+
zones.removeObject(zone);
|
|
681
|
+
} else {
|
|
682
|
+
serviceArea.set?.('zones', nextZones);
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
this.serviceAreaActions.serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
#removeOverlay(resource) {
|
|
692
|
+
if (!resource?.id) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
this.mapManager.removeOverlay?.(resource.id);
|
|
697
|
+
|
|
698
|
+
const layer = resource?.leafletLayer;
|
|
699
|
+
if (layer) {
|
|
700
|
+
this.mapManager.removeLayer?.(layer);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
#refreshResourceContextMenus() {
|
|
705
|
+
for (const serviceArea of Array.from(this.serviceAreaActions.serviceAreas ?? [])) {
|
|
706
|
+
const serviceAreaLayer = this.mapManager.getOverlay(serviceArea?.id) ?? serviceArea?.leafletLayer;
|
|
707
|
+
if (serviceAreaLayer) {
|
|
708
|
+
this.#createServiceAreaContextMenu(serviceArea, serviceAreaLayer);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
for (const zone of Array.from(serviceArea?.zones ?? [])) {
|
|
712
|
+
const zoneLayer = this.mapManager.getOverlay(zone?.id) ?? zone?.leafletLayer;
|
|
713
|
+
if (zoneLayer) {
|
|
714
|
+
this.#createZoneContextMenu(zone, zoneLayer);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
#pruneResourceContextMenus() {
|
|
721
|
+
const activeServiceAreaKeys = new Set();
|
|
722
|
+
const activeZoneKeys = new Set();
|
|
723
|
+
|
|
724
|
+
for (const serviceArea of Array.from(this.serviceAreaActions.serviceAreas ?? [])) {
|
|
725
|
+
activeServiceAreaKeys.add(this.#resourceContextKey('service-area', serviceArea));
|
|
726
|
+
|
|
727
|
+
for (const zone of Array.from(serviceArea?.zones ?? [])) {
|
|
728
|
+
activeZoneKeys.add(this.#resourceContextKey('zone', zone));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
for (const registry of Object.values(this.leafletContextmenuManager.contextMenuRegistry ?? {})) {
|
|
733
|
+
if (registry?.serviceArea && !activeServiceAreaKeys.has(this.#resourceContextKey('service-area', registry.serviceArea))) {
|
|
734
|
+
this.leafletContextmenuManager.removeContextMenu(this.#resourceContextKey('service-area', registry.serviceArea));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (registry?.zone && !activeZoneKeys.has(this.#resourceContextKey('zone', registry.zone))) {
|
|
738
|
+
this.leafletContextmenuManager.removeContextMenu(this.#resourceContextKey('zone', registry.zone));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
579
743
|
#createDriverContextMenu(driver, layer) {
|
|
580
744
|
let items = [
|
|
581
745
|
{
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{perform this.confirmSelection}} @decline={{@onDecline}}>
|
|
2
|
+
<div class="modal-body-container">
|
|
3
|
+
<div class="space-y-3">
|
|
4
|
+
{{#each this.waypointOptions as |option|}}
|
|
5
|
+
<button
|
|
6
|
+
type="button"
|
|
7
|
+
class="w-full rounded-md border px-4 py-3 text-left shadow-sm transition {{if option.isCurrent 'border-blue-500 bg-blue-50 dark:border-blue-600 dark:bg-blue-900' (if option.isSelected 'border-green-500 bg-green-50 dark:border-green-700 dark:bg-green-900' 'border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-700 dark:hover:bg-gray-900')}}"
|
|
8
|
+
disabled={{this.confirmSelection.isRunning}}
|
|
9
|
+
{{on "click" (fn this.selectOption option)}}
|
|
10
|
+
>
|
|
11
|
+
<div class="flex items-start gap-3">
|
|
12
|
+
<div
|
|
13
|
+
class="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full text-xs font-bold {{if option.isCurrent 'bg-blue-600 text-white' 'bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-gray-100'}}"
|
|
14
|
+
>
|
|
15
|
+
{{option.label}}
|
|
16
|
+
</div>
|
|
17
|
+
<div class="min-w-0 flex-1">
|
|
18
|
+
<div class="flex items-center justify-between gap-2">
|
|
19
|
+
<div class="truncate text-sm font-semibold dark:text-gray-100">{{option.title}} - {{or option.waypoint.name option.waypoint.id}}</div>
|
|
20
|
+
{{#if option.isCurrent}}
|
|
21
|
+
<Badge @status="info" class="uppercase">Current</Badge>
|
|
22
|
+
{{else if option.isSelected}}
|
|
23
|
+
<Badge @status="success" class="uppercase">Selected</Badge>
|
|
24
|
+
{{else if option.waypoint.complete}}
|
|
25
|
+
<Badge @status="completed" class="uppercase">Complete</Badge>
|
|
26
|
+
{{else if option.waypoint.status_code}}
|
|
27
|
+
<Badge @status={{dasherize option.waypoint.status_code}} class="uppercase">{{option.waypoint.status_code}}</Badge>
|
|
28
|
+
{{else if option.waypoint.status}}
|
|
29
|
+
<Badge @status={{dasherize option.waypoint.status}} class="uppercase">{{option.waypoint.status}}</Badge>
|
|
30
|
+
{{/if}}
|
|
31
|
+
</div>
|
|
32
|
+
<div class="mt-1 truncate text-xs text-gray-600 dark:text-gray-300">{{or option.waypoint.address option.waypoint.street1}}</div>
|
|
33
|
+
{{#if option.tracking}}
|
|
34
|
+
<div class="mt-1 text-xs font-mono text-gray-500 dark:text-gray-400">{{option.tracking}}</div>
|
|
35
|
+
{{/if}}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</button>
|
|
39
|
+
{{else}}
|
|
40
|
+
<div class="rounded-md border border-dashed border-gray-300 p-6 text-center text-sm text-gray-500 dark:border-gray-700 dark:text-gray-400">
|
|
41
|
+
No waypoint destinations are available.
|
|
42
|
+
</div>
|
|
43
|
+
{{/each}}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</Modal::Default>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
import { buildServiceStopsFromPayload, describeRoutePoint } from '../../utils/route-visualization';
|
|
7
|
+
import { colorForId } from '../../utils/route-colors';
|
|
8
|
+
|
|
9
|
+
export default class ModalsOrderChangeDestinationComponent extends Component {
|
|
10
|
+
@service fetch;
|
|
11
|
+
@service notifications;
|
|
12
|
+
@tracked selectedOption;
|
|
13
|
+
|
|
14
|
+
get order() {
|
|
15
|
+
return this.args.options?.order;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get waypoints() {
|
|
19
|
+
const waypoints = this.order?.payload?.waypoints;
|
|
20
|
+
if (typeof waypoints?.toArray === 'function') {
|
|
21
|
+
return waypoints.toArray();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Array.isArray(waypoints) ? waypoints : Array.from(waypoints ?? []);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get routeColor() {
|
|
28
|
+
return colorForId(this.order?.public_id ?? this.order?.id ?? 'order-route');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get serviceStops() {
|
|
32
|
+
return buildServiceStopsFromPayload(this.order?.payload);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get currentWaypointId() {
|
|
36
|
+
const activeStop = this.order?.tracker_data?.active_stop;
|
|
37
|
+
const payload = this.order?.payload;
|
|
38
|
+
|
|
39
|
+
return activeStop?.uuid ?? activeStop?.public_id ?? payload?.current_waypoint_uuid ?? payload?.current_waypoint;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get waypointOptions() {
|
|
43
|
+
return this.serviceStops.map((stop, index) => ({
|
|
44
|
+
waypoint: stop.place,
|
|
45
|
+
stop,
|
|
46
|
+
index,
|
|
47
|
+
id: this.waypointId(stop.place),
|
|
48
|
+
isCurrent: this.isCurrentWaypoint(stop.place),
|
|
49
|
+
isSelected: this.isSelectedStop(stop),
|
|
50
|
+
label: describeRoutePoint(stop, this.routeColor).label,
|
|
51
|
+
title: describeRoutePoint(stop, this.routeColor).title,
|
|
52
|
+
tracking: this.trackingForStop(stop),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
waypointId(waypoint) {
|
|
57
|
+
return waypoint?.id ?? waypoint?.public_id ?? waypoint?.uuid;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
isCurrentWaypoint(waypoint) {
|
|
61
|
+
return [waypoint?.id, waypoint?.uuid, waypoint?.public_id, waypoint?.waypoint_public_id].includes(this.currentWaypointId);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isSelectedStop(stop) {
|
|
65
|
+
const selectedPlace = this.selectedOption?.waypoint;
|
|
66
|
+
|
|
67
|
+
return [selectedPlace?.id, selectedPlace?.uuid, selectedPlace?.public_id, selectedPlace?.waypoint_public_id].includes(this.waypointId(stop?.place));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
trackingForStop(stop) {
|
|
71
|
+
return stop?.place?.tracking ?? stop?.place?.tracking_number ?? stop?.trackingNumberUuid ?? this.order?.tracking ?? this.order?.tracking_number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@action selectOption(option) {
|
|
75
|
+
if (!option?.isCurrent) {
|
|
76
|
+
this.selectedOption = option;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@task *confirmSelection(modal) {
|
|
81
|
+
const option = this.selectedOption;
|
|
82
|
+
const waypoint = option?.waypoint;
|
|
83
|
+
const waypointId = this.waypointId(waypoint);
|
|
84
|
+
if (!waypointId) {
|
|
85
|
+
modal.stopLoading();
|
|
86
|
+
return this.notifications.warning('Select a destination first.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
modal.startLoading();
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const order = yield this.fetch.patch(`orders/set-destination/${this.order.id}/${waypointId}`);
|
|
93
|
+
this.notifications.success(`Current destination changed to ${waypoint.name ?? waypoint.address ?? waypointId}.`);
|
|
94
|
+
|
|
95
|
+
if (typeof this.args.options?.onChange === 'function') {
|
|
96
|
+
yield this.args.options.onChange(order, waypoint);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
modal.done();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.notifications.serverError(error);
|
|
102
|
+
} finally {
|
|
103
|
+
modal.stopLoading();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
|
2
|
+
<div class="modal-body-container">
|
|
3
|
+
<div class="mb-4 rounded-md border border-gray-200 bg-gray-50 px-4 py-3 dark:border-gray-700 dark:bg-gray-800">
|
|
4
|
+
<div class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">Stop</div>
|
|
5
|
+
<div class="mt-1 text-sm font-semibold text-gray-900 dark:text-gray-100">{{this.stopName}}</div>
|
|
6
|
+
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{or this.place.address this.place.street1}}</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
{{#if this.loadActivity.isRunning}}
|
|
10
|
+
<div class="flex items-center justify-center p-8 text-sm text-gray-500 dark:text-gray-400">
|
|
11
|
+
<FaIcon @icon="spinner" @spin={{true}} class="mr-2" />
|
|
12
|
+
Loading activity...
|
|
13
|
+
</div>
|
|
14
|
+
{{else if this.trackingNumberUuid}}
|
|
15
|
+
<Order::ActivityList @activity={{this.activity}} @resource={{hash status=this.stop.status}} />
|
|
16
|
+
{{else}}
|
|
17
|
+
<div class="min-h-24 pb-4">
|
|
18
|
+
<div class="flex items-center justify-center rounded-xl border border-dashed border-gray-300 bg-gray-50 p-6 dark:border-gray-600 dark:bg-gray-800">
|
|
19
|
+
<div class="text-center">
|
|
20
|
+
<FaIcon @icon="layer-group" @size="2x" class="mb-2 text-gray-400" />
|
|
21
|
+
<div class="text-sm font-medium text-gray-600 dark:text-gray-400">No stop activity</div>
|
|
22
|
+
<div class="mt-1 text-xs text-gray-500 dark:text-gray-500">This stop does not have a separate activity stream.</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
{{/if}}
|
|
27
|
+
</div>
|
|
28
|
+
</Modal::Default>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { task } from 'ember-concurrency';
|
|
5
|
+
|
|
6
|
+
export default class ModalsStopActivityComponent extends Component {
|
|
7
|
+
@service store;
|
|
8
|
+
@service notifications;
|
|
9
|
+
@tracked activity = [];
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.loadActivity.perform();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get stop() {
|
|
17
|
+
return this.args.options?.stop;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get place() {
|
|
21
|
+
return this.stop?.place ?? this.stop;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get order() {
|
|
25
|
+
return this.args.options?.order;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get stopName() {
|
|
29
|
+
return this.place?.name ?? this.place?.address ?? this.place?.id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get trackingNumberUuid() {
|
|
33
|
+
return this.stop?.trackingNumberUuid ?? this.stop?.tracking_number_uuid ?? this.stop?.tracking_number?.uuid ?? this.place?.tracking_number_uuid ?? this.place?.tracking_number?.uuid;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@task *loadActivity() {
|
|
37
|
+
if (!this.trackingNumberUuid) {
|
|
38
|
+
this.activity = [];
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const activity = yield this.store.query('tracking-status', {
|
|
44
|
+
tracking_number_uuid: this.trackingNumberUuid,
|
|
45
|
+
});
|
|
46
|
+
this.activity = activity.toArray();
|
|
47
|
+
return activity;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
this.notifications.serverError(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -16,6 +16,23 @@
|
|
|
16
16
|
</InfoBlock>
|
|
17
17
|
{{/if}}
|
|
18
18
|
|
|
19
|
+
{{#if this.currentWaypoint}}
|
|
20
|
+
<div class="my-4 rounded-md border border-blue-200 bg-blue-50 px-4 py-3 text-blue-900 dark:border-blue-700 dark:bg-blue-900 dark:text-blue-50">
|
|
21
|
+
<div class="text-xs font-semibold uppercase">Updating activity for</div>
|
|
22
|
+
<div class="mt-1 flex items-start justify-between gap-3">
|
|
23
|
+
<div class="min-w-0">
|
|
24
|
+
<div class="truncate text-sm font-semibold">{{or this.currentWaypoint.name this.currentWaypoint.id}}</div>
|
|
25
|
+
<div class="truncate text-xs">{{or (place-address-html this.currentWaypoint showTitle=false) this.currentWaypoint.address this.currentWaypoint.street1}}</div>
|
|
26
|
+
</div>
|
|
27
|
+
{{#if this.currentWaypoint.status_code}}
|
|
28
|
+
<Badge @status={{dasherize this.currentWaypoint.status_code}} class="uppercase">{{this.currentWaypoint.status_code}}</Badge>
|
|
29
|
+
{{else if this.currentWaypoint.status}}
|
|
30
|
+
<Badge @status={{dasherize this.currentWaypoint.status}} class="uppercase">{{this.currentWaypoint.status}}</Badge>
|
|
31
|
+
{{/if}}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
{{/if}}
|
|
35
|
+
|
|
19
36
|
<fieldset class="my-5">
|
|
20
37
|
<legend class="sr-only">
|
|
21
38
|
{{t "modals.order-new-activity.activity-options"}}
|
|
@@ -36,14 +53,18 @@
|
|
|
36
53
|
</div>
|
|
37
54
|
<label for={{concat "activity_" index}} class="ml-3 flex flex-col cursor-pointer w-full">
|
|
38
55
|
<span class="block text-sm font-semibold">
|
|
39
|
-
{{activity.status}}
|
|
56
|
+
{{or activity._resolved_status activity.status}}
|
|
40
57
|
</span>
|
|
41
58
|
<span class="block text-sm">
|
|
42
|
-
{{activity.details}}
|
|
59
|
+
{{or activity._resolved_details activity.details}}
|
|
43
60
|
|
|
44
61
|
{{#if (eq activity.code "dispatched")}}
|
|
45
62
|
<InfoBlock @type="warning" @size="xs" @icon="exclamation-triangle" @text={{t "modals.order-new-activity.dispatch"}} class="mt-1" />
|
|
46
63
|
{{/if}}
|
|
64
|
+
|
|
65
|
+
{{#if activity.require_pod}}
|
|
66
|
+
<InfoBlock @type="warning" @size="xs" @icon="camera" @text="Requires photo proof of delivery" class="mt-1" />
|
|
67
|
+
{{/if}}
|
|
47
68
|
</span>
|
|
48
69
|
</label>
|
|
49
70
|
<div>
|
|
@@ -88,5 +109,26 @@
|
|
|
88
109
|
</div>
|
|
89
110
|
</div>
|
|
90
111
|
</fieldset>
|
|
112
|
+
|
|
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>
|
|
123
|
+
{{#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}}
|
|
130
|
+
{{/if}}
|
|
131
|
+
</div>
|
|
132
|
+
{{/if}}
|
|
91
133
|
</div>
|
|
92
|
-
</Modal::Default>
|
|
134
|
+
</Modal::Default>
|