@fleetbase/fleetops-engine 0.6.71 → 0.6.72
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/RELEASE.md +15 -18
- package/addon/components/admin/tracking-page-settings.hbs +56 -0
- package/addon/components/admin/tracking-page-settings.js +67 -0
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/driver/details.hbs +1 -1
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/equipment/form.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +11 -3
- package/addon/components/maintenance/form.hbs +3 -3
- package/addon/components/map/google-live-map.js +2 -2
- package/addon/components/map/leaflet-live-map.js +45 -32
- package/addon/components/orchestrator/phase-builder.js +2 -1
- package/addon/components/orchestrator-workbench.hbs +7 -1
- package/addon/components/orchestrator-workbench.js +16 -2
- package/addon/components/order/form/route.hbs +1 -1
- package/addon/components/order/form/route.js +13 -1
- package/addon/components/service-rate/details.hbs +1 -1
- package/addon/components/service-rate/form.hbs +1 -1
- package/addon/components/tracking-page/full-view.hbs +276 -0
- package/addon/components/tracking-page/full-view.js +474 -0
- package/addon/components/tracking-page/locale-selector.js +30 -0
- package/addon/components/tracking-page/map.hbs +44 -0
- package/addon/components/tracking-page/map.js +328 -0
- package/addon/components/tracking-page/order-list.hbs +35 -0
- package/addon/components/tracking-page/public-view.hbs +60 -0
- package/addon/components/tracking-page/settings-preview.hbs +21 -0
- package/addon/components/tracking-page/settings-preview.js +23 -0
- package/addon/components/tracking-page/verify-panel.hbs +109 -0
- package/addon/components/tracking-page.hbs +142 -0
- package/addon/components/tracking-page.js +564 -0
- package/addon/components/trailer/form.hbs +2 -2
- package/addon/components/vehicle/form.hbs +3 -3
- package/addon/components/warranty/form.hbs +2 -2
- package/addon/components/widget/live-fleet.js +54 -6
- package/addon/components/widget/proof-of-delivery.hbs +49 -0
- package/addon/components/widget/proof-of-delivery.js +62 -0
- package/addon/components/work-order/form.hbs +3 -3
- package/addon/controllers/operations/orders/index/details.js +19 -3
- package/addon/controllers/operations/scheduler/index.js +45 -14
- package/addon/controllers/settings/index.js +6 -0
- package/addon/controllers/settings/map.js +75 -0
- package/addon/controllers/settings/orchestrator.js +8 -6
- package/addon/controllers/settings/tracking-page.js +212 -0
- package/addon/extension.js +65 -1
- package/addon/helpers/parcel-box-image.js +16 -0
- package/addon/routes/operations/orders/index/new.js +5 -4
- package/addon/routes/operations/scheduler/index.js +1 -1
- package/addon/routes/settings/tracking-page.js +31 -0
- package/addon/routes.js +1 -0
- package/addon/services/location.js +69 -21
- package/addon/services/map-settings.js +28 -0
- package/addon/services/order-allocation.js +1 -1
- package/addon/styles/fleetops-engine.css +1391 -0
- package/addon/templates/settings/map.hbs +46 -0
- package/addon/templates/settings/tracking-page.hbs +318 -0
- package/addon/utils/identity-cell-resource.js +2 -56
- package/addon/utils/map-default-view.js +132 -0
- package/addon/utils/placeholder-images.js +4 -69
- package/addon/utils/resource-descriptors/assets.js +2 -57
- package/addon/utils/resource-descriptors/helpers.js +23 -122
- package/addon/utils/resource-descriptors/operations.js +2 -61
- package/addon/utils/resource-descriptors/people.js +3 -142
- package/addon/utils/resource-descriptors/shared.js +18 -0
- package/addon/utils/tracking-live-source.js +123 -0
- package/addon/utils/tracking-page-theme.js +123 -0
- package/app/components/admin/tracking-page-settings.js +1 -0
- package/app/components/tracking-page/full-view.js +1 -0
- package/app/components/tracking-page/locale-selector.js +1 -0
- package/app/components/tracking-page/map.js +1 -0
- package/app/components/tracking-page/order-list.js +1 -0
- package/app/components/tracking-page/public-view.js +1 -0
- package/app/components/tracking-page/settings-preview.js +1 -0
- package/app/components/tracking-page/verify-panel.js +1 -0
- package/app/components/tracking-page.js +1 -0
- package/app/components/widget/proof-of-delivery.js +1 -0
- package/app/controllers/settings/tracking-page.js +1 -0
- package/app/helpers/parcel-box-image.js +1 -0
- package/app/routes/settings/tracking-page.js +1 -0
- package/app/templates/settings/tracking-page.js +1 -0
- package/app/utils/map-default-view.js +1 -0
- package/app/utils/tracking-live-source.js +1 -0
- package/app/utils/tracking-page-theme.js +1 -0
- package/composer.json +2 -2
- package/extension.json +1 -1
- package/package.json +4 -4
- package/scripts/pest-bootstrap.php +4 -0
- package/server/config/fleetops.php +4 -0
- package/server/migrations/2026_10_08_000001_create_tracking_sessions_tables.php +51 -0
- package/server/migrations/2026_10_10_000001_add_trashed_index_to_device_events.php +40 -0
- package/server/migrations/2026_10_10_000002_add_trashed_index_to_positions.php +40 -0
- package/server/migrations/2026_10_12_000001_add_deleted_at_to_tracking_sessions_tables.php +32 -0
- package/server/resources/views/labels/default.php +2 -2
- package/server/resources/views/labels/entity-label.php +2 -2
- package/server/resources/views/labels/waypoint-label.php +2 -2
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +1 -0
- package/server/src/Console/Commands/PruneTelematicsData.php +101 -26
- package/server/src/Events/OrderChanged.php +257 -0
- package/server/src/Events/OrderDriverUnassigned.php +68 -0
- package/server/src/Exceptions/OrderEditLockedException.php +32 -0
- package/server/src/Expansions/CompanyExpansion.php +3 -2
- package/server/src/Expansions/UserExpansion.php +12 -10
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +108 -18
- package/server/src/Http/Controllers/Api/v1/DriverController.php +95 -10
- package/server/src/Http/Controllers/Api/v1/EntityController.php +66 -0
- package/server/src/Http/Controllers/Api/v1/FleetController.php +10 -4
- package/server/src/Http/Controllers/Api/v1/OrderController.php +139 -36
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +76 -8
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +3 -3
- package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +6 -32
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +5 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +3 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +10 -0
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +16 -2
- package/server/src/Http/Controllers/Internal/v1/InspectionSubmissionController.php +13 -8
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +4 -2
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +93 -11
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +84 -24
- package/server/src/Http/Controllers/Internal/v1/PositionController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/ProofController.php +55 -4
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +303 -10
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +1 -0
- package/server/src/Http/Controllers/Public/TrackingController.php +518 -0
- package/server/src/Http/Filter/ContactFilter.php +1 -1
- package/server/src/Http/Filter/InspectionFormFilter.php +23 -0
- package/server/src/Http/Filter/InspectionSubmissionFilter.php +23 -0
- package/server/src/Http/Filter/MaintenanceFilter.php +23 -0
- package/server/src/Http/Filter/OrderFilter.php +39 -4
- package/server/src/Http/Filter/ProofFilter.php +18 -0
- package/server/src/Http/Filter/RouteFilter.php +18 -0
- package/server/src/Http/Filter/TelematicFilter.php +23 -0
- package/server/src/Http/Filter/WarrantyFilter.php +23 -0
- package/server/src/Http/Requests/CancelOrderRequest.php +2 -1
- package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +14 -1
- package/server/src/Http/Resources/v1/Index/Customer.php +18 -0
- package/server/src/Http/Resources/v1/Index/Order.php +11 -2
- package/server/src/Http/Resources/v1/PublicOrderTracking.php +153 -0
- package/server/src/Http/Resources/v1/TrackingNumber.php +8 -34
- package/server/src/Http/Resources/v1/Zone.php +31 -23
- package/server/src/Jobs/FinalizeApiOrderCreation.php +3 -1
- package/server/src/Jobs/ProcessAllocationJob.php +16 -6
- package/server/src/Jobs/PublishTrackingUpdate.php +50 -0
- package/server/src/Listeners/HandleGeofenceEntered.php +31 -15
- package/server/src/Listeners/HandleOrderCanceled.php +9 -1
- package/server/src/Listeners/HandleUserRemovedFromCompany.php +2 -1
- package/server/src/Listeners/PublishTrackingUpdates.php +27 -0
- package/server/src/Mail/CustomerCredentialsMail.php +4 -2
- package/server/src/Models/AssetConnection.php +3 -0
- package/server/src/Models/Contact.php +3 -0
- package/server/src/Models/Device.php +28 -0
- package/server/src/Models/DeviceEvent.php +7 -0
- package/server/src/Models/Driver.php +20 -7
- package/server/src/Models/Entity.php +12 -4
- package/server/src/Models/Fleet.php +11 -32
- package/server/src/Models/FleetDriver.php +3 -0
- package/server/src/Models/FleetVehicle.php +3 -0
- package/server/src/Models/FuelReport.php +5 -13
- package/server/src/Models/Issue.php +4 -16
- package/server/src/Models/Order.php +200 -9
- package/server/src/Models/Payload.php +227 -35
- package/server/src/Models/Place.php +7 -1
- package/server/src/Models/ServiceArea.php +7 -0
- package/server/src/Models/ServiceRate.php +7 -17
- package/server/src/Models/TrackingNumber.php +101 -2
- package/server/src/Models/TrackingSession.php +33 -0
- package/server/src/Models/TrackingSessionGrant.php +22 -0
- package/server/src/Models/TrackingStatus.php +6 -0
- package/server/src/Models/Vehicle.php +21 -2
- package/server/src/Models/Vendor.php +7 -0
- package/server/src/Models/Waypoint.php +13 -4
- package/server/src/Notifications/OrderAssigned.php +2 -1
- package/server/src/Notifications/OrderCanceled.php +7 -1
- package/server/src/Notifications/OrderChanged.php +87 -0
- package/server/src/Notifications/OrderCompleted.php +2 -1
- package/server/src/Notifications/OrderDispatchFailed.php +2 -1
- package/server/src/Notifications/OrderDispatched.php +2 -1
- package/server/src/Notifications/OrderFailed.php +2 -1
- package/server/src/Notifications/OrderUnassigned.php +66 -0
- package/server/src/Notifications/WaypointCompleted.php +2 -1
- package/server/src/Observers/CompanyUserObserver.php +8 -7
- package/server/src/Observers/DriverObserver.php +6 -1
- package/server/src/Observers/OrderObserver.php +141 -1
- package/server/src/Observers/TrackingNumberObserver.php +18 -6
- package/server/src/Observers/UserObserver.php +2 -1
- package/server/src/Observers/VehicleObserver.php +8 -3
- package/server/src/Orchestration/Engines/RouteSequencingEngine.php +21 -3
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +38 -8
- package/server/src/Orchestration/OrchestrationEngineRegistry.php +61 -1
- package/server/src/Orchestration/Support/OrchestratorSettings.php +61 -0
- package/server/src/Orchestration/Support/RouteMetrics.php +165 -0
- package/server/src/Providers/EventServiceProvider.php +11 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +13 -0
- package/server/src/Support/ApiCacheThrottle.php +76 -0
- package/server/src/Support/LiveOrderQuery.php +3 -1
- package/server/src/Support/OrderEdit.php +365 -0
- package/server/src/Support/OrderEditPolicy.php +292 -0
- package/server/src/Support/ProfileAccountManager.php +54 -1
- package/server/src/Support/ResolvesOrderServiceStops.php +5 -0
- package/server/src/Support/SocketChannels.php +346 -0
- package/server/src/Support/Telematics/TelematicService.php +7 -2
- package/server/src/Support/Telematics/Telemetry/Inbox.php +8 -1
- package/server/src/Support/TrackingChannel.php +55 -0
- package/server/src/Support/TrackingCode.php +242 -0
- package/server/src/Support/TrackingPage/TrackingAccess.php +356 -0
- package/server/src/Support/TrackingPage/TrackingPageConfig.php +264 -0
- package/server/src/Support/TrackingPage/TrackingRecipient.php +111 -0
- package/server/src/Support/TrackingPage/TrackingResolver.php +201 -0
- package/server/src/Support/TrackingPage/TrackingTarget.php +40 -0
- package/server/src/Support/TrackingPage/TrackingView.php +543 -0
- package/server/src/Support/TrackingPublisher.php +261 -0
- package/server/src/Support/TrackingScope.php +182 -0
- package/server/src/Support/TrackingUpdate.php +402 -0
- package/server/src/Support/Utils.php +15 -3
- package/server/src/Traits/HasTrackingNumber.php +2 -0
- package/server/src/routes.php +39 -7
- package/server/tests/ApiCustomerControllerContractsTest.php +54 -7
- package/server/tests/ApiDriverPasswordContractsTest.php +25 -21
- package/server/tests/ApiEntityControllerContractsTest.php +84 -0
- package/server/tests/ApiOrderControllerContractsTest.php +245 -0
- package/server/tests/ApiPayloadControllerContractsTest.php +121 -0
- package/server/tests/ApiPlaceControllerContractsTest.php +1 -1
- package/server/tests/ApiTrackingNumberControllerContractsTest.php +6 -9
- package/server/tests/CompactResourceSerializationTest.php +23 -59
- package/server/tests/ControllerFilterContractsTest.php +1 -1
- package/server/tests/CustomerContactIdentitySafetyTest.php +1 -1
- package/server/tests/EventContractsTest.php +26 -60
- package/server/tests/ExpansionContractsTest.php +15 -2
- package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +29 -3
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +23 -0
- package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +16 -16
- package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +41 -14
- package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +57 -22
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +23 -9
- package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +171 -1
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +110 -14
- package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +7 -5
- package/server/tests/Feature/Http/Internal/PositionControllerReplayChannelTest.php +135 -0
- package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +3 -3
- package/server/tests/Feature/Http/Internal/ProofControllerRecentTest.php +170 -0
- package/server/tests/Feature/Http/Public/TrackingControllerTest.php +495 -0
- package/server/tests/IndexCustomerPhotoTest.php +151 -0
- package/server/tests/ModelAccessorContractsTest.php +4 -3
- package/server/tests/NotificationAndMailContractsTest.php +5 -8
- package/server/tests/ObserverContractsTest.php +72 -10
- package/server/tests/OrderActivityFlowRegressionTest.php +3 -2
- package/server/tests/OrderFilterExecutionTest.php +167 -2
- package/server/tests/ProcessAllocationJobContractsTest.php +20 -0
- package/server/tests/PublicOrderTrackingResourceTest.php +221 -0
- package/server/tests/QueueJobContractsTest.php +2 -1
- package/server/tests/RequestContractsTest.php +1 -1
- package/server/tests/RouteRegistrationExecutionTest.php +26 -0
- package/server/tests/RouteSequencingEngineTest.php +17 -0
- package/server/tests/SettingControllerContractsTest.php +110 -0
- package/server/tests/Support/TrackingPageDatabase.php +200 -0
- package/server/tests/TenantScopingTest.php +206 -0
- package/server/tests/TrackingPageSettingsTest.php +343 -0
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +51 -0
- package/server/tests/Unit/Http/Filter/ContactFilterAddressTest.php +61 -0
- package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +30 -0
- package/server/tests/Unit/Jobs/FinalizeApiOrderCreationDispatchGuardTest.php +266 -0
- package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +182 -22
- package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +5 -0
- package/server/tests/Unit/Models/DriverAssignVehicleTest.php +81 -0
- package/server/tests/Unit/Models/FleetTest.php +4 -3
- package/server/tests/Unit/Models/OrderCancelReasonTest.php +143 -0
- package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +60 -0
- package/server/tests/Unit/Models/OrderPayloadRouteTest.php +4 -2
- package/server/tests/Unit/Models/PayloadTest.php +18 -4
- package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +2 -2
- package/server/tests/Unit/Models/TrackingNumberTest.php +67 -6
- package/server/tests/Unit/Models/VehicleTest.php +16 -2
- package/server/tests/Unit/Models/WaypointTest.php +4 -3
- package/server/tests/Unit/Notifications/OrderChangeNotificationsTest.php +59 -0
- package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +3 -3
- package/server/tests/Unit/Observers/DriverDeletionCleanupTest.php +158 -0
- package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +20 -0
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +3 -3
- package/server/tests/Unit/Observers/OrderAssignmentObserverTest.php +100 -0
- package/server/tests/Unit/Observers/OrderDeleteCascadeTest.php +213 -0
- package/server/tests/Unit/Observers/VehicleObserverDeleteTest.php +67 -0
- package/server/tests/Unit/Orchestration/EngineSelectionAndRouteMetricsTest.php +336 -0
- package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +8 -1
- package/server/tests/Unit/Support/ApiCacheDependenciesTest.php +347 -0
- package/server/tests/Unit/Support/OrderEditPolicyTest.php +156 -0
- package/server/tests/Unit/Support/OrderOperationsTest.php +514 -0
- package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +4 -3
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +141 -0
- package/server/tests/Unit/Support/QuerySeamsTest.php +5 -1
- package/server/tests/Unit/Support/SocketChannelsTest.php +396 -0
- package/server/tests/Unit/Support/TrackingChannelTest.php +232 -0
- package/server/tests/Unit/Support/TrackingCodeTest.php +186 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingAccessTest.php +200 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingRecipientTest.php +51 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingResolverTest.php +181 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingViewTest.php +479 -0
- package/server/tests/Unit/Support/TrackingPublisherTest.php +425 -0
- package/server/tests/Unit/Support/TrackingUpdateTest.php +363 -0
- package/server/tests/Unit/Support/UtilsAdditionalTest.php +9 -0
- package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +25 -0
- package/server/tests/VehicleResourceContractsTest.php +46 -0
- package/server/tests/VroomOrchestrationEngineTest.php +34 -1
- package/server/tests/ZoneGeofenceSettingsResourceTest.php +121 -0
- package/translations/ar-ae.yml +274 -0
- package/translations/bg-bg.yaml +274 -0
- package/translations/en-us.yaml +287 -0
- package/translations/es-pa.yaml +274 -0
- package/translations/fr-fr.yaml +274 -0
- package/translations/mn-mn.yaml +274 -0
- package/translations/pt-br.yaml +274 -0
- package/translations/ru-ru.yaml +276 -2
- package/translations/uk-ua.yaml +275 -1
- package/translations/vi-vn.yaml +274 -0
- package/addon/components/cell/contact-identity.hbs +0 -1
- package/addon/components/cell/customer-identity.hbs +0 -1
- package/addon/components/cell/driver-identity.hbs +0 -1
- package/addon/components/cell/fleet-identity.hbs +0 -1
- package/addon/components/cell/order-identity.hbs +0 -1
- package/addon/components/cell/place-identity.hbs +0 -1
- package/addon/components/cell/vehicle-identity.hbs +0 -1
- package/addon/components/cell/vendor-identity.hbs +0 -1
- package/addon/components/contact/pill.hbs +0 -24
- package/addon/components/contact/summary.hbs +0 -1
- package/addon/components/customer/pill.hbs +0 -24
- package/addon/components/customer/summary.hbs +0 -1
- package/addon/components/driver/pill.hbs +0 -24
- package/addon/components/driver/summary.hbs +0 -1
- package/addon/components/fleet/pill.hbs +0 -24
- package/addon/components/fleet/summary.hbs +0 -1
- package/addon/components/order/pill.hbs +0 -24
- package/addon/components/order/summary.hbs +0 -1
- package/addon/components/place/pill.hbs +0 -24
- package/addon/components/place/summary.hbs +0 -1
- package/addon/components/select-option/contact.hbs +0 -12
- package/addon/components/select-option/customer.hbs +0 -12
- package/addon/components/select-option/driver.hbs +0 -12
- package/addon/components/select-option/fleet.hbs +0 -12
- package/addon/components/select-option/order.hbs +0 -12
- package/addon/components/select-option/place.hbs +0 -12
- package/addon/components/select-option/vehicle.hbs +0 -12
- package/addon/components/select-option/vendor.hbs +0 -12
- package/addon/components/vehicle/pill.hbs +0 -24
- package/addon/components/vehicle/summary.hbs +0 -1
- package/addon/components/vendor/pill.hbs +0 -24
- package/addon/components/vendor/summary.hbs +0 -1
- package/app/components/cell/contact-identity.js +0 -1
- package/app/components/cell/customer-identity.js +0 -1
- package/app/components/cell/driver-identity.js +0 -1
- package/app/components/cell/fleet-identity.js +0 -1
- package/app/components/cell/order-identity.js +0 -1
- package/app/components/cell/place-identity.js +0 -1
- package/app/components/cell/vehicle-identity.js +0 -1
- package/app/components/cell/vendor-identity.js +0 -1
- package/app/components/contact/pill.js +0 -1
- package/app/components/contact/summary.js +0 -1
- package/app/components/customer/pill.js +0 -1
- package/app/components/customer/summary.js +0 -1
- package/app/components/driver/pill.js +0 -1
- package/app/components/driver/summary.js +0 -1
- package/app/components/fleet/pill.js +0 -1
- package/app/components/fleet/summary.js +0 -1
- package/app/components/order/pill.js +0 -1
- package/app/components/order/summary.js +0 -1
- package/app/components/place/pill.js +0 -1
- package/app/components/place/summary.js +0 -1
- package/app/components/select-option/contact.js +0 -1
- package/app/components/select-option/customer.js +0 -1
- package/app/components/select-option/driver.js +0 -1
- package/app/components/select-option/fleet.js +0 -1
- package/app/components/select-option/order.js +0 -1
- package/app/components/select-option/place.js +0 -1
- package/app/components/select-option/vehicle.js +0 -1
- package/app/components/select-option/vendor.js +0 -1
- package/app/components/vehicle/pill.js +0 -1
- package/app/components/vehicle/summary.js +0 -1
- package/app/components/vendor/pill.js +0 -1
- package/app/components/vendor/summary.js +0 -1
|
@@ -9,6 +9,7 @@ import { renderCompleted, waitForInsertedAndSized } from '@fleetbase/ember-ui/ut
|
|
|
9
9
|
import LeafletTrackingMarkerComponent from '../leaflet-tracking-marker';
|
|
10
10
|
import ensureLeafletDrawEditNamespace from '../../utils/leaflet-draw-namespace-guard';
|
|
11
11
|
import { DEFAULT_LEAFLET_TILE_ATTRIBUTION } from '../../utils/leaflet-tile-url';
|
|
12
|
+
import { FIT_VEHICLES_MAX_ZOOM, positionedBounds } from '../../utils/map-default-view';
|
|
12
13
|
|
|
13
14
|
const DEFAULT_CENTER = [1.31, 103.85];
|
|
14
15
|
const DEFAULT_ZOOM = 11;
|
|
@@ -56,6 +57,8 @@ export default class WidgetLiveFleetComponent extends Component {
|
|
|
56
57
|
#map = null;
|
|
57
58
|
#mapSetPromise = null;
|
|
58
59
|
#resolveMapSet = null;
|
|
60
|
+
/** Fit to the vehicles only on the first load, never on the periodic reconcile. */
|
|
61
|
+
#fittedToVehicles = false;
|
|
59
62
|
|
|
60
63
|
constructor() {
|
|
61
64
|
super(...arguments);
|
|
@@ -152,15 +155,57 @@ export default class WidgetLiveFleetComponent extends Component {
|
|
|
152
155
|
return this.drivers.length + this.vehicles.length;
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
/**
|
|
156
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Bounds around the positioned vehicles when the company chose "Fit to my
|
|
160
|
+
* vehicles" (Settings > Map), else null.
|
|
161
|
+
*/
|
|
162
|
+
get vehicleBounds() {
|
|
163
|
+
if (!this.mapSettings.fitMapToVehicles) return null;
|
|
164
|
+
return positionedBounds(this.vehicles.map((vehicle) => ({ location: [vehicle.lat, vehicle.lng] })));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Where the map opens, in order: the positioned vehicles (when fitting is
|
|
169
|
+
* on), the company's default centre, the first positioned marker, the
|
|
170
|
+
* centre of the company's country, the hardcoded default.
|
|
171
|
+
*/
|
|
172
|
+
get initialView() {
|
|
173
|
+
const bounds = this.vehicleBounds;
|
|
174
|
+
if (bounds) {
|
|
175
|
+
const [[south, west], [north, east]] = bounds;
|
|
176
|
+
return { center: [(south + north) / 2, (west + east) / 2], zoom: DEFAULT_ZOOM, bounds };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const defaultCenter = this.mapSettings.defaultMapCenter;
|
|
180
|
+
if (defaultCenter) return { center: [defaultCenter.latitude, defaultCenter.longitude], zoom: defaultCenter.zoom };
|
|
181
|
+
|
|
157
182
|
const first = this.drivers[0] ?? this.vehicles[0];
|
|
158
|
-
if (first?.lat && first?.lng) return [first.lat, first.lng];
|
|
159
|
-
|
|
183
|
+
if (first?.lat && first?.lng) return { center: [first.lat, first.lng], zoom: DEFAULT_ZOOM };
|
|
184
|
+
|
|
185
|
+
const countryCenter = this.mapSettings.companyCountryCenter;
|
|
186
|
+
if (countryCenter) return { center: [countryCenter.latitude, countryCenter.longitude], zoom: countryCenter.zoom };
|
|
187
|
+
|
|
188
|
+
return { center: DEFAULT_CENTER, zoom: DEFAULT_ZOOM };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get center() {
|
|
192
|
+
return this.initialView.center;
|
|
160
193
|
}
|
|
161
194
|
|
|
162
195
|
get zoom() {
|
|
163
|
-
return
|
|
196
|
+
return this.initialView.zoom;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Frame the positioned vehicles once, after the first data load. */
|
|
200
|
+
fitToVehicles(map) {
|
|
201
|
+
if (this.#fittedToVehicles) return false;
|
|
202
|
+
|
|
203
|
+
const bounds = this.vehicleBounds;
|
|
204
|
+
if (!bounds || typeof map?.fitBounds !== 'function') return false;
|
|
205
|
+
|
|
206
|
+
map.fitBounds(bounds, { padding: [30, 30], maxZoom: FIT_VEHICLES_MAX_ZOOM });
|
|
207
|
+
this.#fittedToVehicles = true;
|
|
208
|
+
return true;
|
|
164
209
|
}
|
|
165
210
|
|
|
166
211
|
statusColor(status) {
|
|
@@ -182,9 +227,12 @@ export default class WidgetLiveFleetComponent extends Component {
|
|
|
182
227
|
// container before we add markers. Without this, markers added to a
|
|
183
228
|
// not-yet-sized map silently no-op (the data arrives, count chip ticks
|
|
184
229
|
// up, but nothing paints).
|
|
185
|
-
yield this.ensureInteractive();
|
|
230
|
+
const map = yield this.ensureInteractive();
|
|
186
231
|
this.data = yield this.fetch.get('fleet-ops/analytics/live-fleet');
|
|
187
232
|
this.error = null;
|
|
233
|
+
// The fit choice lives in the company's map settings, loaded in the constructor.
|
|
234
|
+
yield this.mapSettings.load();
|
|
235
|
+
this.fitToVehicles(map);
|
|
188
236
|
} catch (e) {
|
|
189
237
|
this.error = e?.message ?? 'Failed to load live fleet';
|
|
190
238
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<div class="fleet-ops-widget fleet-ops-proof-of-delivery flex flex-col h-full {{@class}}" data-test-proof-of-delivery-widget ...attributes>
|
|
2
|
+
<div class="flex items-center justify-between px-3 py-1.5 border dark:border-gray-700 border-gray-200 dark:bg-gray-800 bg-gray-50 rounded-t-md shadow-sm">
|
|
3
|
+
<span class="text-xs font-bold text-black dark:text-gray-100">{{t "widget.proof-of-delivery.title"}}</span>
|
|
4
|
+
<FaIcon @icon="camera" class="text-gray-400" />
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="flex-1 p-2 border border-t-0 dark:border-gray-700 border-gray-200 rounded-b-md bg-white dark:bg-gray-900 overflow-auto min-h-0">
|
|
8
|
+
{{#if this.load.isRunning}}
|
|
9
|
+
<div class="flex items-center justify-center h-full"><Spinner /></div>
|
|
10
|
+
{{else if this.error}}
|
|
11
|
+
<div class="flex items-center justify-center h-full text-xs text-red-500">{{this.error}}</div>
|
|
12
|
+
{{else if this.photos.length}}
|
|
13
|
+
<div class="grid grid-cols-3 gap-2">
|
|
14
|
+
{{#each this.photos as |proof|}}
|
|
15
|
+
<div class="min-w-0" data-test-proof-of-delivery-photo={{proof.id}}>
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
class="block w-full aspect-square rounded-md overflow-hidden border dark:border-gray-700 border-gray-200 bg-gray-100 dark:bg-gray-800 hover:opacity-90"
|
|
19
|
+
title={{or proof.remarks (t "widget.proof-of-delivery.open-photo")}}
|
|
20
|
+
aria-label={{t "widget.proof-of-delivery.open-photo"}}
|
|
21
|
+
{{on "click" (fn this.openPhoto proof)}}
|
|
22
|
+
>
|
|
23
|
+
<img src={{proof.url}} alt={{or proof.remarks (t "widget.proof-of-delivery.title")}} loading="lazy" class="w-full h-full object-cover" />
|
|
24
|
+
</button>
|
|
25
|
+
<div class="mt-0.5 leading-tight">
|
|
26
|
+
{{#if proof.order_id}}
|
|
27
|
+
<LinkTo
|
|
28
|
+
@route={{this.orderRoute}}
|
|
29
|
+
@model={{proof.order_id}}
|
|
30
|
+
class="block text-[10px] font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200 truncate"
|
|
31
|
+
title={{proof.customer_name}}
|
|
32
|
+
data-test-proof-of-delivery-order
|
|
33
|
+
>{{proof.order_id}}</LinkTo>
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#if proof.timeAgo}}
|
|
36
|
+
<div class="text-[9px] text-gray-500 dark:text-gray-400 truncate">{{proof.timeAgo}}</div>
|
|
37
|
+
{{/if}}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{{/each}}
|
|
41
|
+
</div>
|
|
42
|
+
{{else}}
|
|
43
|
+
<div class="flex flex-col items-center justify-center h-full text-center text-gray-500 dark:text-gray-400" data-test-proof-of-delivery-empty>
|
|
44
|
+
<FaIcon @icon="camera" class="text-lg mb-1 text-gray-300 dark:text-gray-600" />
|
|
45
|
+
<div class="text-[11px]">{{t "widget.proof-of-delivery.empty"}}</div>
|
|
46
|
+
</div>
|
|
47
|
+
{{/if}}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
import { inject as service } from '@ember/service';
|
|
5
|
+
import { getOwner } from '@ember/application';
|
|
6
|
+
import { task } from 'ember-concurrency';
|
|
7
|
+
import { formatDistanceToNowStrict, isValid } from 'date-fns';
|
|
8
|
+
import config from '../../config/environment';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The latest proof of delivery photos across the fleet, each linking to its order.
|
|
12
|
+
*/
|
|
13
|
+
export default class WidgetProofOfDeliveryComponent extends Component {
|
|
14
|
+
static widgetId = 'fleet-ops-proof-of-delivery-widget';
|
|
15
|
+
static limit = 12;
|
|
16
|
+
|
|
17
|
+
@service fetch;
|
|
18
|
+
|
|
19
|
+
@tracked proofs = [];
|
|
20
|
+
@tracked error = null;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
this.load.perform();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get photos() {
|
|
28
|
+
return (this.proofs ?? [])
|
|
29
|
+
.filter((proof) => proof?.url)
|
|
30
|
+
.map((proof) => ({
|
|
31
|
+
...proof,
|
|
32
|
+
timeAgo: this.timeAgo(proof.captured_at),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get orderRoute() {
|
|
37
|
+
// Dashboard widgets render under the host owner, outside the engine's routing context.
|
|
38
|
+
return getOwner(this).mountPoint ? 'operations.orders.index.details' : `${config.mountedEngineRoutePrefix}.operations.orders.index.details`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
timeAgo(capturedAt) {
|
|
42
|
+
const date = capturedAt ? new Date(capturedAt) : null;
|
|
43
|
+
|
|
44
|
+
return date && isValid(date) ? formatDistanceToNowStrict(date, { addSuffix: true }) : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@action openPhoto(proof) {
|
|
48
|
+
if (proof?.url) {
|
|
49
|
+
window.open(proof.url, '_blank', 'noopener,noreferrer');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@task({ restartable: true }) *load() {
|
|
54
|
+
this.error = null;
|
|
55
|
+
try {
|
|
56
|
+
const response = yield this.fetch.get('fleet-ops/proofs/recent', { limit: WidgetProofOfDeliveryComponent.limit });
|
|
57
|
+
this.proofs = response?.proofs ?? [];
|
|
58
|
+
} catch (err) {
|
|
59
|
+
this.error = err?.message ?? 'Failed to load';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
</div>
|
|
176
176
|
<InputGroup @name="Opened At">
|
|
177
177
|
<DatePicker
|
|
178
|
-
@value={{or (format-date-fns @resource.opened_at "
|
|
178
|
+
@value={{or (format-date-fns @resource.opened_at "yyyy-MM-dd") null}}
|
|
179
179
|
@onChange={{fn (mut @resource.opened_at)}}
|
|
180
180
|
@placeholder="DD-MM-YYYY"
|
|
181
181
|
class="filter-date-input w-full flex-1"
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
</InputGroup>
|
|
184
184
|
<InputGroup @name="Due At">
|
|
185
185
|
<DatePicker
|
|
186
|
-
@value={{or (format-date-fns @resource.due_at "
|
|
186
|
+
@value={{or (format-date-fns @resource.due_at "yyyy-MM-dd") null}}
|
|
187
187
|
@onChange={{fn (mut @resource.due_at)}}
|
|
188
188
|
@placeholder="DD-MM-YYYY"
|
|
189
189
|
class="filter-date-input w-full flex-1"
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
</InputGroup>
|
|
192
192
|
<InputGroup @name="Closed At">
|
|
193
193
|
<DatePicker
|
|
194
|
-
@value={{or (format-date-fns @resource.closed_at "
|
|
194
|
+
@value={{or (format-date-fns @resource.closed_at "yyyy-MM-dd") null}}
|
|
195
195
|
@onChange={{fn (mut @resource.closed_at)}}
|
|
196
196
|
@placeholder="DD-MM-YYYY"
|
|
197
197
|
class="filter-date-input w-full flex-1"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Controller, { inject as controller } from '@ember/controller';
|
|
2
|
+
import { getOwner } from '@ember/application';
|
|
2
3
|
import { tracked } from '@glimmer/tracking';
|
|
3
4
|
import { action } from '@ember/object';
|
|
4
5
|
import { inject as service } from '@ember/service';
|
|
@@ -78,6 +79,9 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
78
79
|
return [
|
|
79
80
|
{
|
|
80
81
|
route: 'operations.orders.index.details.index',
|
|
82
|
+
// Pass the order explicitly: without it the link's href is generated from whatever
|
|
83
|
+
// transition is in flight, and leaving for a shallower route throws in the router.
|
|
84
|
+
model: this.model?.public_id,
|
|
81
85
|
label: 'Overview',
|
|
82
86
|
icon: 'folder-open',
|
|
83
87
|
},
|
|
@@ -219,10 +223,22 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
219
223
|
}
|
|
220
224
|
|
|
221
225
|
@task *refresh() {
|
|
226
|
+
yield this.refreshDetails();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Reload only the order details route. A bare `hostRouter.refresh()` re-runs every route
|
|
231
|
+
* from the application down (console, the engine's application `beforeModel`, the orders
|
|
232
|
+
* list), so a socket event during navigation leaves a slow refresh in flight.
|
|
233
|
+
*/
|
|
234
|
+
async refreshDetails() {
|
|
222
235
|
try {
|
|
223
|
-
|
|
236
|
+
await getOwner(this).lookup('route:operations.orders.index.details').refresh();
|
|
224
237
|
} catch (error) {
|
|
225
|
-
|
|
238
|
+
// A refresh superseded by a newer transition is expected, not an error.
|
|
239
|
+
if (error?.name !== 'TransitionAborted') {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
244
|
|
|
@@ -246,7 +262,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
246
262
|
this.model,
|
|
247
263
|
async (_msg, { reloadable }) => {
|
|
248
264
|
if (reloadable) {
|
|
249
|
-
await this.
|
|
265
|
+
await this.refreshDetails();
|
|
250
266
|
}
|
|
251
267
|
},
|
|
252
268
|
{ debounceMs: 250 }
|
|
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
|
|
|
4
4
|
import { action, computed } from '@ember/object';
|
|
5
5
|
import { isNone } from '@ember/utils';
|
|
6
6
|
import { isValid as isValidDate } from 'date-fns';
|
|
7
|
+
import { later } from '@ember/runloop';
|
|
7
8
|
import { task } from 'ember-concurrency';
|
|
8
9
|
import isObject from '@fleetbase/ember-core/utils/is-object';
|
|
9
10
|
import isJson from '@fleetbase/ember-core/utils/is-json';
|
|
@@ -11,6 +12,9 @@ import createFullCalendarEventFromOrder from '../../../utils/create-full-calenda
|
|
|
11
12
|
import createFullCalendarEventFromScheduleItem from '../../../utils/create-full-calendar-event-from-schedule-item';
|
|
12
13
|
import toCalendarDate from '../../../utils/to-calendar-date';
|
|
13
14
|
|
|
15
|
+
// The ember-core socket service subscribes ~300 ms after listen() is called.
|
|
16
|
+
const SOCKET_SUBSCRIBE_SETTLE_MS = 500;
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* OperationsSchedulerIndexController
|
|
16
20
|
*
|
|
@@ -722,28 +726,55 @@ export default class OperationsSchedulerIndexController extends Controller {
|
|
|
722
726
|
// Real-Time Socket Subscriptions
|
|
723
727
|
// -------------------------------------------------------------------------
|
|
724
728
|
|
|
729
|
+
// Names of the socket channels this board opened, so teardown closes only those
|
|
730
|
+
// and leaves every other subscription (chat, notifications, ...) alone.
|
|
731
|
+
_socketChannelNames = new Set();
|
|
732
|
+
|
|
725
733
|
@action async subscribeToRealTimeUpdates() {
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
734
|
+
const drivers = this.drivers ?? [];
|
|
735
|
+
await Promise.all(
|
|
736
|
+
drivers.map((driver) => {
|
|
737
|
+
if (!driver?.id) return;
|
|
738
|
+
const channelName = `driver.${driver.id}`;
|
|
739
|
+
if (this._socketChannelNames.has(channelName)) return;
|
|
740
|
+
this._socketChannelNames.add(channelName);
|
|
741
|
+
return this.socket.listen(channelName, (payload) => this._handleDriverSocketEvent(payload));
|
|
742
|
+
})
|
|
743
|
+
);
|
|
732
744
|
}
|
|
733
745
|
|
|
734
746
|
@action unsubscribeFromRealTimeUpdates() {
|
|
735
|
-
|
|
736
|
-
|
|
747
|
+
const names = this._socketChannelNames;
|
|
748
|
+
this._socketChannelNames = new Set();
|
|
749
|
+
if (!this.socket || names.size === 0) return;
|
|
750
|
+
|
|
751
|
+
if (typeof this.socket.closeChannel === 'function') {
|
|
752
|
+
names.forEach((name) => this.socket.closeChannel(name));
|
|
753
|
+
return;
|
|
737
754
|
}
|
|
755
|
+
|
|
756
|
+
const remaining = this._closeOpenedChannels(names);
|
|
757
|
+
if (remaining.size === 0) return;
|
|
758
|
+
|
|
759
|
+
// The socket service subscribes after a short delay, so a channel requested just before
|
|
760
|
+
// teardown may not exist yet: sweep once more after that delay.
|
|
761
|
+
later(this, () => this._closeOpenedChannels(remaining), SOCKET_SUBSCRIBE_SETTLE_MS);
|
|
738
762
|
}
|
|
739
763
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
764
|
+
/**
|
|
765
|
+
* Close the socket's channels with the given names, except any the board has opened again
|
|
766
|
+
* since. Returns the names that had no channel yet.
|
|
767
|
+
*/
|
|
768
|
+
_closeOpenedChannels(names) {
|
|
769
|
+
const remaining = new Set([...names].filter((name) => !this._socketChannelNames.has(name)));
|
|
770
|
+
const channels = Array.isArray(this.socket.channels) ? this.socket.channels : [];
|
|
771
|
+
for (const channel of channels) {
|
|
772
|
+
if (!names.has(channel?.name) || this._socketChannelNames.has(channel.name)) continue;
|
|
773
|
+
remaining.delete(channel.name);
|
|
774
|
+
channel.close();
|
|
746
775
|
}
|
|
776
|
+
|
|
777
|
+
return remaining;
|
|
747
778
|
}
|
|
748
779
|
|
|
749
780
|
_handleDriverSocketEvent({ event, data } = {}) {
|
|
@@ -28,6 +28,12 @@ export default class SettingsIndexController extends Controller {
|
|
|
28
28
|
description: 'Keep commerce, metadata, and visual conventions aligned.',
|
|
29
29
|
links: [
|
|
30
30
|
{ label: 'Payments', route: 'settings.payments', icon: 'cash-register', description: 'Payment setup for operational commerce workflows.' },
|
|
31
|
+
{
|
|
32
|
+
label: 'Tracking Page',
|
|
33
|
+
route: 'settings.tracking-page',
|
|
34
|
+
icon: 'magnifying-glass-location',
|
|
35
|
+
description: 'The page your customers use to follow deliveries: address, branding and how they verify.',
|
|
36
|
+
},
|
|
31
37
|
{
|
|
32
38
|
label: 'Telematics',
|
|
33
39
|
route: 'settings.telematics',
|
|
@@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
|
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
5
|
import { task } from 'ember-concurrency';
|
|
6
6
|
import MAP_PROVIDER_OPTIONS from '../../utils/map-provider-options';
|
|
7
|
+
import { isLatitude, isLongitude, isZoom, toCoordinate } from '../../utils/map-default-view';
|
|
7
8
|
|
|
8
9
|
export default class SettingsMapController extends Controller {
|
|
9
10
|
@service fetch;
|
|
@@ -11,6 +12,7 @@ export default class SettingsMapController extends Controller {
|
|
|
11
12
|
@service intl;
|
|
12
13
|
@service mapManager;
|
|
13
14
|
@service mapSettings;
|
|
15
|
+
@service location;
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Available map provider options for the provider selector.
|
|
@@ -39,6 +41,21 @@ export default class SettingsMapController extends Controller {
|
|
|
39
41
|
@tracked showGoogleMapsTrafficLayer = false;
|
|
40
42
|
@tracked showGoogleMapsTransitLayer = false;
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The company's default live map view. Latitude and longitude are both
|
|
46
|
+
* set or both null; zoom is optional.
|
|
47
|
+
*/
|
|
48
|
+
@tracked defaultMapLatitude = null;
|
|
49
|
+
@tracked defaultMapLongitude = null;
|
|
50
|
+
@tracked defaultMapZoom = null;
|
|
51
|
+
@tracked fitMapToVehicles = false;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Bumped to re-create the coordinates input, which only reads its value
|
|
55
|
+
* when it is constructed (e.g. after clearing the default center).
|
|
56
|
+
*/
|
|
57
|
+
@tracked defaultCenterInputKey = 0;
|
|
58
|
+
|
|
42
59
|
/**
|
|
43
60
|
* Whether settings have been loaded from the server.
|
|
44
61
|
*
|
|
@@ -74,6 +91,36 @@ export default class SettingsMapController extends Controller {
|
|
|
74
91
|
return this.mapProvider === 'leaflet';
|
|
75
92
|
}
|
|
76
93
|
|
|
94
|
+
/**
|
|
95
|
+
* The default center as a GeoJSON point for the coordinates input, or null.
|
|
96
|
+
*
|
|
97
|
+
* @memberof SettingsMapController
|
|
98
|
+
* @return {Object|null}
|
|
99
|
+
*/
|
|
100
|
+
get defaultMapCenterPoint() {
|
|
101
|
+
// Not null: CoordinatesInput#isPoint reads `.type` on any object, and typeof null is "object".
|
|
102
|
+
if (!this.hasDefaultMapCenter) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { type: 'Point', coordinates: [this.defaultMapLongitude, this.defaultMapLatitude] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get hasDefaultMapCenter() {
|
|
110
|
+
return isLatitude(this.defaultMapLatitude) && isLongitude(this.defaultMapLongitude);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The zoom to save: a whole number from 1 to 20, else null.
|
|
115
|
+
*
|
|
116
|
+
* @memberof SettingsMapController
|
|
117
|
+
* @return {Number|null}
|
|
118
|
+
*/
|
|
119
|
+
get normalizedDefaultMapZoom() {
|
|
120
|
+
const zoom = toCoordinate(this.defaultMapZoom);
|
|
121
|
+
return isZoom(zoom) ? zoom : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
77
124
|
// ─── Actions ───────────────────────────────────────────────────────────────
|
|
78
125
|
|
|
79
126
|
/**
|
|
@@ -86,6 +133,24 @@ export default class SettingsMapController extends Controller {
|
|
|
86
133
|
this.mapProvider = option?.value ?? option ?? 'leaflet';
|
|
87
134
|
}
|
|
88
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Receives coordinates typed into, or picked on the map of, the default
|
|
138
|
+
* center input.
|
|
139
|
+
*
|
|
140
|
+
* @param {{latitude: *, longitude: *}} coordinates
|
|
141
|
+
* @memberof SettingsMapController
|
|
142
|
+
*/
|
|
143
|
+
@action setDefaultMapCenter({ latitude, longitude } = {}) {
|
|
144
|
+
this.defaultMapLatitude = toCoordinate(latitude);
|
|
145
|
+
this.defaultMapLongitude = toCoordinate(longitude);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@action clearDefaultMapCenter() {
|
|
149
|
+
this.defaultMapLatitude = null;
|
|
150
|
+
this.defaultMapLongitude = null;
|
|
151
|
+
this.defaultCenterInputKey++;
|
|
152
|
+
}
|
|
153
|
+
|
|
89
154
|
@task *saveSettings() {
|
|
90
155
|
const settings = {
|
|
91
156
|
mapProvider: this.mapProvider,
|
|
@@ -94,12 +159,19 @@ export default class SettingsMapController extends Controller {
|
|
|
94
159
|
googleMapsMapType: this.googleMapsMapType,
|
|
95
160
|
showGoogleMapsTrafficLayer: this.showGoogleMapsTrafficLayer,
|
|
96
161
|
showGoogleMapsTransitLayer: this.showGoogleMapsTransitLayer,
|
|
162
|
+
defaultMapLatitude: this.hasDefaultMapCenter ? this.defaultMapLatitude : null,
|
|
163
|
+
defaultMapLongitude: this.hasDefaultMapCenter ? this.defaultMapLongitude : null,
|
|
164
|
+
defaultMapZoom: this.normalizedDefaultMapZoom,
|
|
165
|
+
fitMapToVehicles: this.fitMapToVehicles,
|
|
97
166
|
};
|
|
98
167
|
|
|
99
168
|
try {
|
|
100
169
|
const response = yield this.fetch.post('fleet-ops/settings/map', { settings });
|
|
101
170
|
this.mapSettings.applySettings(response);
|
|
102
171
|
|
|
172
|
+
// Re-resolve where maps open so an open live map moves to the new default.
|
|
173
|
+
this.location.relocate();
|
|
174
|
+
|
|
103
175
|
if (this.mapManager && typeof this.mapManager.setActiveProvider === 'function') {
|
|
104
176
|
this.mapManager.setActiveProvider(this.mapProvider);
|
|
105
177
|
}
|
|
@@ -124,6 +196,9 @@ export default class SettingsMapController extends Controller {
|
|
|
124
196
|
this.googleMapsMapType = response?.googleMapsMapType ?? 'roadmap';
|
|
125
197
|
this.showGoogleMapsTrafficLayer = Boolean(response?.showGoogleMapsTrafficLayer);
|
|
126
198
|
this.showGoogleMapsTransitLayer = Boolean(response?.showGoogleMapsTransitLayer);
|
|
199
|
+
this.setDefaultMapCenter({ latitude: response?.defaultMapLatitude, longitude: response?.defaultMapLongitude });
|
|
200
|
+
this.defaultMapZoom = response?.defaultMapZoom ?? null;
|
|
201
|
+
this.fitMapToVehicles = Boolean(response?.fitMapToVehicles);
|
|
127
202
|
this.settingsLoaded = true;
|
|
128
203
|
} catch (error) {
|
|
129
204
|
this.notifications.serverError(error);
|
|
@@ -17,7 +17,8 @@ export default class SettingsOrchestratorController extends Controller {
|
|
|
17
17
|
@service intl;
|
|
18
18
|
@service('orchestration-engine') engineRegistry;
|
|
19
19
|
|
|
20
|
-
@tracked allocationEngine = '
|
|
20
|
+
@tracked allocationEngine = 'greedy';
|
|
21
|
+
@tracked serverEngines = null;
|
|
21
22
|
@tracked autoAllocateOnCreate = false;
|
|
22
23
|
@tracked autoReallocateOnComplete = false;
|
|
23
24
|
@tracked maxTravelTimeSeconds = 3600;
|
|
@@ -26,18 +27,19 @@ export default class SettingsOrchestratorController extends Controller {
|
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Engine options for the selector dropdown.
|
|
29
|
-
* Populated from the
|
|
30
|
-
*
|
|
30
|
+
* Populated from the engines registered on the server (the ones a run can
|
|
31
|
+
* actually use), falling back to the frontend allocation-engine registry.
|
|
31
32
|
*/
|
|
32
33
|
get engineOptions() {
|
|
33
|
-
return this.engineRegistry.availableEngines;
|
|
34
|
+
return this.serverEngines ?? this.engineRegistry.availableEngines;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
@task *loadSettings() {
|
|
37
38
|
this.isLoading = true;
|
|
38
39
|
try {
|
|
39
|
-
const settings = yield this.fetch.get('fleet-ops/settings/orchestrator-settings');
|
|
40
|
-
this.
|
|
40
|
+
const [settings, engines] = yield Promise.all([this.fetch.get('fleet-ops/settings/orchestrator-settings'), this.fetch.get('fleet-ops/orchestrator/engines').catch(() => null)]);
|
|
41
|
+
this.serverEngines = engines?.engines ?? null;
|
|
42
|
+
this.allocationEngine = settings.allocation_engine ?? 'greedy';
|
|
41
43
|
this.autoAllocateOnCreate = settings.auto_allocate_on_create ?? false;
|
|
42
44
|
this.autoReallocateOnComplete = settings.auto_reallocate_on_complete ?? false;
|
|
43
45
|
this.maxTravelTimeSeconds = settings.max_travel_time_seconds ?? 3600;
|