@fleetbase/fleetops-engine 0.6.49 → 0.6.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -67
- package/addon/components/contact/panel-header.hbs +11 -48
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/details.hbs +1 -2
- package/addon/components/customer/form.hbs +62 -50
- package/addon/components/customer/form.js +27 -1
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +50 -2
- package/addon/components/customer/orders.js +67 -5
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/home/getting-started-guidance.hbs +113 -0
- package/addon/components/home/getting-started-guidance.js +107 -0
- package/addon/components/issue/details/comments.hbs +3 -0
- package/addon/components/issue/details/comments.js +3 -0
- package/addon/components/issue/details/correspondence.hbs +9 -0
- package/addon/components/issue/details/correspondence.js +83 -0
- package/addon/components/issue/details/documents.hbs +69 -0
- package/addon/components/issue/details/documents.js +85 -0
- package/addon/components/issue/details.hbs +220 -59
- package/addon/components/issue/details.js +245 -0
- package/addon/components/issue/form.hbs +19 -1
- package/addon/components/issue/panel-header.hbs +43 -0
- package/addon/components/issue/panel-header.js +22 -0
- package/addon/components/issue/timeline.hbs +40 -0
- package/addon/components/issue/timeline.js +45 -0
- package/addon/components/leaflet-tracking-marker.js +3 -0
- package/addon/components/map/drawer/position-listing.js +3 -0
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +201 -20
- package/addon/components/map/order-list-overlay.hbs +4 -4
- package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
- package/addon/components/modals/convert-customer-to-vendor.js +55 -0
- package/addon/components/modals/issue-assign.hbs +20 -0
- package/addon/components/modals/issue-assign.js +49 -0
- package/addon/components/modals/issue-close.hbs +30 -0
- package/addon/components/modals/issue-close.js +73 -0
- package/addon/components/modals/issue-status.hbs +20 -0
- package/addon/components/modals/issue-status.js +43 -0
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/reset-customer-credentials.js +12 -6
- 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 +78 -2
- package/addon/components/modals/update-order-activity.js +197 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- 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 +13 -2
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/proof.hbs +40 -38
- package/addon/components/order/details/proof.js +31 -4
- 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/details.hbs +5 -5
- package/addon/components/order/details.js +19 -1
- 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/header.hbs +1 -1
- 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 -29
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +42 -1
- package/addon/components/service-rate/form.hbs +119 -1
- package/addon/components/service-rate/form.js +59 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/vendor/personnel-manager.hbs +166 -0
- package/addon/components/vendor/personnel-manager.js +143 -0
- package/addon/components/widget/live-fleet.hbs +1 -0
- package/addon/components/widget/live-fleet.js +2 -7
- package/addon/components/widget/revenue-trend.hbs +1 -1
- package/addon/components/widget/revenue-trend.js +75 -2
- package/addon/controllers/management/contacts/customers/details.js +31 -13
- package/addon/controllers/management/contacts/customers.js +16 -5
- package/addon/controllers/management/contacts/index/details.js +31 -13
- package/addon/controllers/management/contacts/index.js +7 -0
- package/addon/controllers/management/issues/index/details.js +52 -6
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/management/vendors/index/details.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +15 -7
- package/addon/controllers/operations/orders/index.js +15 -1
- package/addon/extension.js +19 -12
- package/addon/helpers/format-duration.js +1 -38
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/initializers/load-leaflet-assets.js +5 -6
- package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
- package/addon/routes/management/issues/index/details.js +1 -1
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +231 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/getting-started.js +44 -0
- package/addon/services/issue-actions.js +71 -0
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/leaflet-map-manager.js +2 -0
- package/addon/services/map-adapter/leaflet.js +19 -0
- package/addon/services/service-areas.js +3 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +1127 -1
- package/addon/templates/management/contacts/customers/details.hbs +1 -1
- package/addon/templates/management/contacts/index/details.hbs +1 -1
- package/addon/templates/management/contacts.hbs +2 -2
- package/addon/templates/management/issues/index/details/index.hbs +3 -1
- package/addon/templates/management/issues/index/details.hbs +3 -2
- package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
- package/addon/templates/operations/orders/index/details/index.hbs +2 -2
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
- package/addon/templates/operations/service-rates/index/new.hbs +1 -0
- package/addon/templates/settings/payments/onboard.hbs +8 -8
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
- package/addon/utils/leaflet-plugin-loader.js +219 -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/addon/utils/setup-customer-portal.js +4 -64
- package/app/components/home/getting-started-guidance.js +1 -0
- package/app/components/issue/details/comments.js +1 -0
- package/app/components/issue/details/correspondence.js +1 -0
- package/app/components/issue/details/documents.js +1 -0
- package/app/components/issue/panel-header.js +1 -0
- package/app/components/modals/convert-customer-to-vendor.js +1 -0
- package/app/components/modals/issue-assign.js +1 -0
- package/app/components/modals/issue-close.js +1 -0
- package/app/components/modals/issue-status.js +1 -0
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/vendor/personnel-manager.js +1 -0
- package/app/helpers/format-duration.js +1 -1
- package/app/services/getting-started.js +1 -0
- package/app/utils/leaflet-draw-namespace-guard.js +1 -0
- package/app/utils/leaflet-plugin-loader.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +5 -5
- 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/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
- package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
- package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
- package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
- package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/resources/views/mail/customer-credentials.blade.php +16 -13
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Expansions/UserFilterExpansion.php +28 -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/IssueController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
- 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/ContactController.php +154 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
- package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
- 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/CreateIssueRequest.php +4 -0
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
- 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/Index/Order.php +12 -9
- package/server/src/Http/Resources/v1/Issue.php +25 -0
- package/server/src/Http/Resources/v1/Order.php +20 -18
- 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/Mail/CustomerCredentialsMail.php +5 -6
- package/server/src/Models/Contact.php +122 -66
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Issue.php +47 -6
- package/server/src/Models/Order.php +62 -10
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceArea.php +1 -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/VendorPersonnel.php +10 -1
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Models/Zone.php +1 -1
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/PurchaseRateObserver.php +20 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Observers/ZoneObserver.php +56 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +10 -2
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/GettingStarted.php +144 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/PlaceSearch.php +282 -0
- package/server/src/Support/ResolvesOrderServiceStops.php +522 -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 +76 -10
- package/server/tests/AnalyticsRoutesTest.php +8 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/GettingStartedStatusTest.php +21 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/ar-ae.yml +9 -0
- package/translations/bg-bg.yaml +9 -0
- package/translations/en-us.yaml +12 -0
- package/translations/es-pa.yaml +9 -0
- package/translations/fr-fr.yaml +9 -0
- package/translations/mn-mn.yaml +9 -0
- package/translations/pt-br.yaml +9 -0
- package/translations/ru-ru.yaml +9 -0
- package/translations/vi-vn.yaml +9 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -27,7 +27,7 @@ export default class OperationsServiceRatesIndexRoute extends Route {
|
|
|
27
27
|
model(params) {
|
|
28
28
|
return this.store.query('service-rate', {
|
|
29
29
|
...params,
|
|
30
|
-
with: ['parcelFees', 'rateFees', 'zone', 'serviceArea'],
|
|
30
|
+
with: ['parcelFees', 'rateFees.zone', 'rateFees.serviceArea', 'zone', 'serviceArea'],
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
}
|
package/addon/routes.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import ResourceActionService from '@fleetbase/ember-core/services/resource-action';
|
|
2
2
|
import { inject as service } from '@ember/service';
|
|
3
|
-
import { action } from '@ember/object';
|
|
3
|
+
import { action, get } from '@ember/object';
|
|
4
|
+
|
|
5
|
+
const INTERNAL_NAMESPACE = 'int/v1';
|
|
4
6
|
|
|
5
7
|
export default class ContactActionsService extends ResourceActionService {
|
|
8
|
+
@service fetch;
|
|
6
9
|
@service placeActions;
|
|
10
|
+
@service notifications;
|
|
11
|
+
@service hostRouter;
|
|
12
|
+
@service('universe/extension-manager') extensionManager;
|
|
7
13
|
|
|
8
14
|
constructor() {
|
|
9
15
|
super(...arguments);
|
|
@@ -112,4 +118,228 @@ export default class ContactActionsService extends ResourceActionService {
|
|
|
112
118
|
}
|
|
113
119
|
);
|
|
114
120
|
}
|
|
121
|
+
|
|
122
|
+
hasLinkedUser(contact) {
|
|
123
|
+
const user = contact?.user;
|
|
124
|
+
return Boolean(contact?.user_uuid || (user && (get(user, 'id') || get(user, 'uuid'))));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
linkedUserStatus(contact) {
|
|
128
|
+
const user = contact?.user;
|
|
129
|
+
return (user ? (get(user, 'status') ?? get(user, 'session_status')) : null) ?? 'active';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
hasInactiveLogin(contact) {
|
|
133
|
+
return ['inactive', 'disabled', 'suspended'].includes(this.linkedUserStatus(contact));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
isCustomerPortalInstalled() {
|
|
137
|
+
return this.extensionManager.isInstalled('@fleetbase/customer-portal-engine');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
accountActionButton(contact, options = {}) {
|
|
141
|
+
if (!this.hasLinkedUser(contact)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
icon: 'ellipsis-h',
|
|
147
|
+
iconPrefix: 'fas',
|
|
148
|
+
renderInPlace: true,
|
|
149
|
+
items: this.accountActionItems(contact, options),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
accountActionItems(contact, options = {}) {
|
|
154
|
+
const actions = [
|
|
155
|
+
{
|
|
156
|
+
text: 'Reset Password',
|
|
157
|
+
icon: 'key',
|
|
158
|
+
fn: () => this.openResetPasswordModal(contact),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
text: 'Send Credentials',
|
|
162
|
+
icon: 'paper-plane',
|
|
163
|
+
fn: () => this.confirmSendCredentials(contact),
|
|
164
|
+
},
|
|
165
|
+
this.hasInactiveLogin(contact)
|
|
166
|
+
? {
|
|
167
|
+
text: 'Reactivate Login',
|
|
168
|
+
icon: 'unlock',
|
|
169
|
+
fn: () => this.confirmReactivatePortalLogin(contact),
|
|
170
|
+
}
|
|
171
|
+
: {
|
|
172
|
+
text: 'Deactivate Login',
|
|
173
|
+
icon: 'lock',
|
|
174
|
+
fn: () => this.confirmDeactivatePortalLogin(contact),
|
|
175
|
+
class: 'text-red-500 hover:text-red-600',
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
if (this.isCustomerPortalInstalled()) {
|
|
180
|
+
actions.push(
|
|
181
|
+
{
|
|
182
|
+
separator: true,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
text: 'Convert to Vendor',
|
|
186
|
+
icon: 'building',
|
|
187
|
+
fn: () => this.openConvertToVendorModal(contact, options),
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return actions;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
accountRowActionItems(options = {}) {
|
|
196
|
+
const hasCustomerPortal = this.isCustomerPortalInstalled();
|
|
197
|
+
const hasLinkedUser = (contact) => this.hasLinkedUser(contact);
|
|
198
|
+
|
|
199
|
+
const actions = [
|
|
200
|
+
{
|
|
201
|
+
label: 'Reset Password',
|
|
202
|
+
icon: 'key',
|
|
203
|
+
fn: (contact) => this.openResetPasswordModal(contact),
|
|
204
|
+
isVisible: hasLinkedUser,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
label: 'Send Credentials',
|
|
208
|
+
icon: 'paper-plane',
|
|
209
|
+
fn: (contact) => this.confirmSendCredentials(contact),
|
|
210
|
+
isVisible: hasLinkedUser,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
label: 'Deactivate Login',
|
|
214
|
+
icon: 'lock',
|
|
215
|
+
class: 'text-red-500 hover:text-red-600',
|
|
216
|
+
fn: (contact) => this.confirmDeactivatePortalLogin(contact),
|
|
217
|
+
isVisible: (contact) => hasLinkedUser(contact) && !this.hasInactiveLogin(contact),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
label: 'Reactivate Login',
|
|
221
|
+
icon: 'unlock',
|
|
222
|
+
fn: (contact) => this.confirmReactivatePortalLogin(contact),
|
|
223
|
+
isVisible: (contact) => hasLinkedUser(contact) && this.hasInactiveLogin(contact),
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
if (hasCustomerPortal) {
|
|
228
|
+
actions.push({
|
|
229
|
+
label: 'Convert to Vendor',
|
|
230
|
+
icon: 'building',
|
|
231
|
+
fn: (contact) => this.openConvertToVendorModal(contact, options),
|
|
232
|
+
isVisible: hasLinkedUser,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return actions;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
openResetPasswordModal(contact) {
|
|
240
|
+
this.modalsManager.show('modals/reset-customer-credentials', {
|
|
241
|
+
customer: contact,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
openConvertToVendorModal(contact, options = {}) {
|
|
246
|
+
this.modalsManager.show('modals/convert-customer-to-vendor', {
|
|
247
|
+
customer: contact,
|
|
248
|
+
onConverted: (vendor) => {
|
|
249
|
+
if (typeof options.onConverted === 'function') {
|
|
250
|
+
options.onConverted(vendor);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (vendor?.id) {
|
|
255
|
+
this.hostRouter.transitionTo('console.fleet-ops.management.vendors.index.details', vendor.id);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
confirmSendCredentials(contact) {
|
|
262
|
+
this.confirmLoginAction(contact, {
|
|
263
|
+
title: 'Send Portal Credentials',
|
|
264
|
+
body: 'Generate a new temporary password and email this contact their portal credentials.',
|
|
265
|
+
acceptButtonText: 'Send Credentials',
|
|
266
|
+
endpoint: 'customers/send-credentials',
|
|
267
|
+
successMessage: 'Portal credentials sent.',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
confirmDeactivatePortalLogin(contact) {
|
|
272
|
+
this.confirmLoginAction(contact, {
|
|
273
|
+
title: 'Deactivate Login',
|
|
274
|
+
body: 'Deactivate portal access for this contact. Their profile and history will be preserved.',
|
|
275
|
+
acceptButtonText: 'Deactivate Login',
|
|
276
|
+
acceptButtonScheme: 'danger',
|
|
277
|
+
endpoint: 'customers/deactivate-portal-login',
|
|
278
|
+
successMessage: 'Portal login deactivated.',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
confirmReactivatePortalLogin(contact) {
|
|
283
|
+
this.confirmLoginAction(contact, {
|
|
284
|
+
title: 'Reactivate Login',
|
|
285
|
+
body: 'Reactivate portal access for this contact.',
|
|
286
|
+
acceptButtonText: 'Reactivate Login',
|
|
287
|
+
endpoint: 'customers/reactivate-portal-login',
|
|
288
|
+
successMessage: 'Portal login reactivated.',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
confirmLoginAction(contact, { title, body, acceptButtonText, acceptButtonScheme, endpoint, successMessage }) {
|
|
293
|
+
this.modalsManager.confirm({
|
|
294
|
+
title,
|
|
295
|
+
body,
|
|
296
|
+
acceptButtonText,
|
|
297
|
+
acceptButtonScheme,
|
|
298
|
+
confirm: async (modal) => {
|
|
299
|
+
modal.startLoading();
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const response = await this.fetch.post(endpoint, { customer: contact?.id }, { namespace: INTERNAL_NAMESPACE });
|
|
303
|
+
await this.updateContactFromResponse(contact, response);
|
|
304
|
+
this.notifications.success(successMessage);
|
|
305
|
+
modal.done();
|
|
306
|
+
} catch (error) {
|
|
307
|
+
this.notifications.serverError(error);
|
|
308
|
+
modal.stopLoading();
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async updateContactFromResponse(contact, response) {
|
|
315
|
+
const payload = response.customer ?? response.contact;
|
|
316
|
+
|
|
317
|
+
if (!payload || typeof contact?.setProperties !== 'function') {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
contact.setProperties(
|
|
322
|
+
this.withoutIdentityFields({
|
|
323
|
+
user_uuid: payload.user_uuid,
|
|
324
|
+
})
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
if (payload.user) {
|
|
328
|
+
const user = await contact.user;
|
|
329
|
+
|
|
330
|
+
if (typeof user?.setProperties === 'function') {
|
|
331
|
+
user.setProperties(this.withoutIdentityFields(payload.user));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
withoutIdentityFields(payload = {}) {
|
|
337
|
+
const attributes = { ...payload };
|
|
338
|
+
|
|
339
|
+
delete attributes.id;
|
|
340
|
+
delete attributes.uuid;
|
|
341
|
+
delete attributes.public_id;
|
|
342
|
+
|
|
343
|
+
return attributes;
|
|
344
|
+
}
|
|
115
345
|
}
|
|
@@ -9,6 +9,7 @@ import Service, { inject as service } from '@ember/service';
|
|
|
9
9
|
import { action } from '@ember/object';
|
|
10
10
|
import { debug } from '@ember/debug';
|
|
11
11
|
import { dasherize } from '@ember/string';
|
|
12
|
+
import { later } from '@ember/runloop';
|
|
12
13
|
import toMultiPolygon from '../utils/to-multi-polygon';
|
|
13
14
|
|
|
14
15
|
export default class GeofenceService extends Service {
|
|
@@ -173,9 +174,11 @@ export default class GeofenceService extends Service {
|
|
|
173
174
|
},
|
|
174
175
|
{
|
|
175
176
|
callback: (serviceArea) => {
|
|
176
|
-
this.#
|
|
177
|
-
|
|
177
|
+
this.#finishDraftSave((draftLayer) => {
|
|
178
|
+
this.#reloadThenFocusServiceArea(serviceArea, { draftLayer });
|
|
179
|
+
});
|
|
178
180
|
},
|
|
181
|
+
refresh: false,
|
|
179
182
|
}
|
|
180
183
|
);
|
|
181
184
|
return;
|
|
@@ -184,7 +187,7 @@ export default class GeofenceService extends Service {
|
|
|
184
187
|
if (this._draftSession.mode === 'zone') {
|
|
185
188
|
const serviceArea = this._draftSession.serviceArea;
|
|
186
189
|
this.zoneActions.modal.create(
|
|
187
|
-
{ border: geoJson,
|
|
190
|
+
{ border: geoJson, service_area: serviceArea },
|
|
188
191
|
{
|
|
189
192
|
decline: (modal) => {
|
|
190
193
|
this.#resetDraftSession({ hideControl: true, removeDraft: true });
|
|
@@ -193,10 +196,11 @@ export default class GeofenceService extends Service {
|
|
|
193
196
|
},
|
|
194
197
|
{
|
|
195
198
|
callback: (zone) => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
this.#finishDraftSave((draftLayer) => {
|
|
200
|
+
this.#reloadThenFocusZone(serviceArea, zone, { draftLayer });
|
|
201
|
+
});
|
|
199
202
|
},
|
|
203
|
+
refresh: false,
|
|
200
204
|
}
|
|
201
205
|
);
|
|
202
206
|
return;
|
|
@@ -246,19 +250,19 @@ export default class GeofenceService extends Service {
|
|
|
246
250
|
} else {
|
|
247
251
|
record.setProperties({
|
|
248
252
|
border: geoJson,
|
|
249
|
-
|
|
250
|
-
serviceArea: selectedServiceArea,
|
|
253
|
+
service_area: selectedServiceArea,
|
|
251
254
|
});
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
return record.save().then((savedRecord) => {
|
|
255
|
-
this.#resetDraftSession({ hideControl: true, removeDraft: true });
|
|
256
|
-
|
|
257
258
|
if (selectedLayerType === 'Service Area') {
|
|
258
|
-
this.#
|
|
259
|
+
this.#finishDraftSave((draftLayer) => {
|
|
260
|
+
this.#reloadThenFocusServiceArea(savedRecord, { draftLayer });
|
|
261
|
+
});
|
|
259
262
|
} else {
|
|
260
|
-
|
|
261
|
-
|
|
263
|
+
this.#finishDraftSave((draftLayer) => {
|
|
264
|
+
this.#reloadThenFocusZone(selectedServiceArea, savedRecord, { draftLayer });
|
|
265
|
+
});
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
return savedRecord;
|
|
@@ -289,28 +293,168 @@ export default class GeofenceService extends Service {
|
|
|
289
293
|
this._draftSession = null;
|
|
290
294
|
}
|
|
291
295
|
|
|
296
|
+
#finishDraftSave(callback) {
|
|
297
|
+
const draftLayer = this._draftSession?.draftLayer ?? null;
|
|
298
|
+
callback?.(draftLayer);
|
|
299
|
+
this.#resetDraftSession({ hideControl: true, removeDraft: false });
|
|
300
|
+
}
|
|
301
|
+
|
|
292
302
|
async #editPolygonLayer(originalLayer, { focusBounds = null } = {}) {
|
|
293
303
|
return this.mapManager.editPolygon(originalLayer, { focusBounds });
|
|
294
304
|
}
|
|
295
305
|
|
|
296
306
|
#showZone(zone, { pin = false } = {}) {
|
|
297
|
-
const
|
|
307
|
+
const overlay = this.mapManager.getOverlay(zone.id);
|
|
308
|
+
const layer = overlay ?? zone?.leafletLayer ?? null;
|
|
298
309
|
this.#setLayerPinned(layer, pin);
|
|
299
|
-
|
|
310
|
+
if (overlay) {
|
|
311
|
+
this.mapManager.showPolygon(zone.id);
|
|
312
|
+
} else if (layer) {
|
|
313
|
+
this.mapManager.showLayer?.(layer);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#includeServiceArea(serviceArea) {
|
|
318
|
+
if (!serviceArea?.id) return;
|
|
319
|
+
|
|
320
|
+
const currentServiceAreas = this.serviceAreaActions.serviceAreas;
|
|
321
|
+
if (!currentServiceAreas) {
|
|
322
|
+
this.serviceAreaActions.serviceAreas = [serviceArea];
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const serviceAreas = Array.from(currentServiceAreas);
|
|
327
|
+
const exists = serviceAreas.some((existingServiceArea) => existingServiceArea?.id === serviceArea.id);
|
|
328
|
+
if (exists) return;
|
|
329
|
+
|
|
330
|
+
this.serviceAreaActions.serviceAreas = [...serviceAreas, serviceArea];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#includeZone(serviceArea, zone) {
|
|
334
|
+
if (!serviceArea?.id || !zone?.id) return;
|
|
335
|
+
|
|
336
|
+
const zones = serviceArea.zones;
|
|
337
|
+
if (!zones) {
|
|
338
|
+
serviceArea.set?.('zones', [zone]);
|
|
339
|
+
this.serviceAreaActions.serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const exists = Array.from(zones).some((existingZone) => existingZone?.id === zone.id);
|
|
344
|
+
if (exists) return;
|
|
345
|
+
|
|
346
|
+
if (typeof zones.pushObject === 'function') {
|
|
347
|
+
zones.pushObject(zone);
|
|
348
|
+
} else if (typeof zones.addObject === 'function') {
|
|
349
|
+
zones.addObject(zone);
|
|
350
|
+
} else if (typeof zones.push === 'function') {
|
|
351
|
+
zones.push(zone);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.serviceAreaActions.serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#reloadThenFocusServiceArea(serviceArea, options = {}) {
|
|
358
|
+
this.#reloadServiceAreas()
|
|
359
|
+
.then((serviceAreas) => {
|
|
360
|
+
const canonicalServiceArea = this.#findResource(serviceAreas, serviceArea) ?? serviceArea;
|
|
361
|
+
this.#focusServiceAreaAfterCreate(canonicalServiceArea, options);
|
|
362
|
+
})
|
|
363
|
+
.catch(() => {
|
|
364
|
+
this.#includeServiceArea(serviceArea);
|
|
365
|
+
this.#focusServiceAreaAfterCreate(serviceArea, options);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
#reloadThenFocusZone(serviceArea, zone, options = {}) {
|
|
370
|
+
this.#reloadServiceAreas()
|
|
371
|
+
.then((serviceAreas) => {
|
|
372
|
+
const canonicalServiceArea = this.#findResource(serviceAreas, serviceArea) ?? serviceArea;
|
|
373
|
+
const canonicalZone = this.#findResource(canonicalServiceArea?.zones, zone) ?? zone;
|
|
374
|
+
this.#focusZoneAfterCreate(canonicalZone, options);
|
|
375
|
+
})
|
|
376
|
+
.catch(() => {
|
|
377
|
+
this.#includeZone(serviceArea, zone);
|
|
378
|
+
this.#focusZoneAfterCreate(zone, options);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#reloadServiceAreas() {
|
|
383
|
+
return Promise.resolve(this.serviceAreaActions.loadAll.perform()).then(() => this.serviceAreaActions.serviceAreas ?? []);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#findResource(resources, resource) {
|
|
387
|
+
if (!resource || !resources) return null;
|
|
388
|
+
|
|
389
|
+
const resourceId = resource.id ?? resource.public_id;
|
|
390
|
+
const publicId = resource.public_id ?? resource.id;
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
Array.from(resources).find((candidate) => {
|
|
394
|
+
return candidate?.id === resourceId || candidate?.id === publicId || candidate?.public_id === publicId || candidate?.public_id === resourceId;
|
|
395
|
+
}) ?? null
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#focusServiceAreaAfterCreate(serviceArea, options = {}) {
|
|
400
|
+
this.#focusWhenReady([serviceArea], () => this.focusServiceArea(serviceArea), options);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#focusZoneAfterCreate(zone, options = {}) {
|
|
404
|
+
this.#focusWhenReady([zone], () => this.focusZone(zone), options);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#focusWhenReady(models, focus, { attempts = 60, delay = 100, draftLayer = null } = {}) {
|
|
408
|
+
const overlays = models
|
|
409
|
+
.map((model) => {
|
|
410
|
+
const overlay = this.mapManager.getOverlay(model?.id);
|
|
411
|
+
if (overlay) {
|
|
412
|
+
return overlay;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return model?.leafletLayer === draftLayer ? null : model?.leafletLayer;
|
|
416
|
+
})
|
|
417
|
+
.filter(Boolean);
|
|
418
|
+
const allRegistered = overlays.length === models.length;
|
|
419
|
+
|
|
420
|
+
if (allRegistered || attempts <= 0) {
|
|
421
|
+
focus();
|
|
422
|
+
if (draftLayer && !draftLayer.__removedAfterCreate && !overlays.includes(draftLayer)) {
|
|
423
|
+
draftLayer.__removedAfterCreate = true;
|
|
424
|
+
this.mapManager.removeLayer(draftLayer);
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
later(this, () => this.#focusWhenReady(models, focus, { attempts: attempts - 1, delay, draftLayer }), delay);
|
|
300
430
|
}
|
|
301
431
|
|
|
302
432
|
#showServiceArea(serviceArea, { pin = false } = {}) {
|
|
303
433
|
[serviceArea, ...(serviceArea.zones ?? [])].forEach((model) => {
|
|
304
|
-
const
|
|
434
|
+
const overlay = this.mapManager.getOverlay(model.id);
|
|
435
|
+
const layer = overlay ?? model?.leafletLayer ?? null;
|
|
305
436
|
this.#setLayerPinned(layer, pin);
|
|
306
|
-
|
|
437
|
+
if (overlay) {
|
|
438
|
+
this.mapManager.showPolygon(model.id);
|
|
439
|
+
} else if (layer) {
|
|
440
|
+
this.mapManager.showLayer?.(layer);
|
|
441
|
+
}
|
|
307
442
|
});
|
|
308
443
|
}
|
|
309
444
|
|
|
310
445
|
#hideServiceArea(serviceArea) {
|
|
311
446
|
[serviceArea, ...(serviceArea.zones ?? [])].forEach((model) => {
|
|
312
|
-
|
|
313
|
-
|
|
447
|
+
const overlay = this.mapManager.getOverlay(model.id);
|
|
448
|
+
const layer = model?.leafletLayer;
|
|
449
|
+
|
|
450
|
+
this.#setLayerPinned(layer ?? overlay, false);
|
|
451
|
+
if (overlay) {
|
|
452
|
+
this.mapManager.hidePolygon(model.id);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (layer && layer !== overlay) {
|
|
456
|
+
this.mapManager.hideLayer?.(layer);
|
|
457
|
+
}
|
|
314
458
|
});
|
|
315
459
|
}
|
|
316
460
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Service from '@ember/service';
|
|
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 GettingStartedService extends Service {
|
|
7
|
+
@service fetch;
|
|
8
|
+
|
|
9
|
+
@tracked data = null;
|
|
10
|
+
@tracked error = null;
|
|
11
|
+
|
|
12
|
+
get isCompleted() {
|
|
13
|
+
return this.data?.is_completed === true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get steps() {
|
|
17
|
+
return this.data?.steps ?? [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get recommendations() {
|
|
21
|
+
return this.data?.recommendations ?? [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get progress() {
|
|
25
|
+
return this.data?.progress ?? { completed: 0, total: 0, percent: 0 };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get nextStepKey() {
|
|
29
|
+
return this.data?.next_step;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get nextStep() {
|
|
33
|
+
return this.steps.find((step) => step.key === this.nextStepKey) ?? this.steps.find((step) => !step.completed);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@task({ drop: true }) *load() {
|
|
37
|
+
try {
|
|
38
|
+
this.data = yield this.fetch.get('fleet-ops/getting-started/status');
|
|
39
|
+
this.error = null;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
this.error = error?.message ?? 'Failed to load getting started status';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -2,6 +2,7 @@ import ResourceActionService from '@fleetbase/ember-core/services/resource-actio
|
|
|
2
2
|
import { inject as service } from '@ember/service';
|
|
3
3
|
import { action } from '@ember/object';
|
|
4
4
|
import { format } from 'date-fns';
|
|
5
|
+
import { issueStatuses } from '../utils/fleet-ops-options';
|
|
5
6
|
|
|
6
7
|
export default class IssueActionsService extends ResourceActionService {
|
|
7
8
|
@service driverActions;
|
|
@@ -92,6 +93,76 @@ export default class IssueActionsService extends ResourceActionService {
|
|
|
92
93
|
},
|
|
93
94
|
};
|
|
94
95
|
|
|
96
|
+
get closedStatuses() {
|
|
97
|
+
return ['closed', 'resolved', 'completed'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
openStatusModal(issue, options = {}) {
|
|
101
|
+
return this.modalsManager.show('modals/issue-status', {
|
|
102
|
+
issue,
|
|
103
|
+
statusOptions: issueStatuses,
|
|
104
|
+
onSaved: options.onSaved,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
openAssignModal(issue, options = {}) {
|
|
109
|
+
return this.modalsManager.show('modals/issue-assign', {
|
|
110
|
+
issue,
|
|
111
|
+
onSaved: options.onSaved,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
openCloseIssueModal(issue, options = {}) {
|
|
116
|
+
return this.modalsManager.show('modals/issue-close', {
|
|
117
|
+
issue,
|
|
118
|
+
onSaved: options.onSaved,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
confirmReopenIssue(issue, options = {}) {
|
|
123
|
+
this.modalsManager.confirm({
|
|
124
|
+
title: `Re-open ${issue.public_id || 'issue'}?`,
|
|
125
|
+
body: 'This will move the issue back into an active re-opened state and clear the active resolution date.',
|
|
126
|
+
acceptButtonText: 'Re-open Issue',
|
|
127
|
+
acceptButtonIcon: 'rotate-left',
|
|
128
|
+
confirm: async (modal) => {
|
|
129
|
+
modal.startLoading();
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const meta = issue.meta ?? {};
|
|
133
|
+
const reopenHistory = Array.isArray(meta.reopen_history) ? meta.reopen_history : [];
|
|
134
|
+
const user = this.currentUser.user;
|
|
135
|
+
|
|
136
|
+
issue.status = 're_opened';
|
|
137
|
+
issue.resolved_at = null;
|
|
138
|
+
issue.meta = {
|
|
139
|
+
...meta,
|
|
140
|
+
reopen_history: [
|
|
141
|
+
...reopenHistory,
|
|
142
|
+
{
|
|
143
|
+
reopened_at: new Date().toISOString(),
|
|
144
|
+
reopened_by_uuid: user?.id,
|
|
145
|
+
reopened_by_name: user?.name,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
await issue.save();
|
|
151
|
+
this.notifications.success('Issue re-opened.');
|
|
152
|
+
|
|
153
|
+
if (typeof options.onSaved === 'function') {
|
|
154
|
+
await options.onSaved(issue);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
modal.done();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
this.notifications.serverError(error);
|
|
160
|
+
modal.stopLoading();
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
95
166
|
@action async viewDriver(issue) {
|
|
96
167
|
const driver = await issue.loadDriver();
|
|
97
168
|
if (driver) {
|
|
@@ -85,6 +85,33 @@ export default class LeafletContextmenuManagerService extends Service {
|
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Remove a registered context menu and detach it from its layer when possible.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} registryName - The public context menu registry name.
|
|
92
|
+
* @returns {Object|null} The removed registry, or null when none existed.
|
|
93
|
+
*/
|
|
94
|
+
removeContextMenu(registryName) {
|
|
95
|
+
const internalRegistryName = this.createInternalRegistryName(registryName);
|
|
96
|
+
const registry = this.contextMenuRegistry[internalRegistryName];
|
|
97
|
+
|
|
98
|
+
if (!registry) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
registry.contextmenuApi?.removeAllItems?.();
|
|
104
|
+
registry.layer?.unbindContextMenu?.();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// Context menus are disposable UI state; stale layer cleanup should not block record deletion.
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
delete this.contextMenuRegistry[internalRegistryName];
|
|
110
|
+
this.contextMenuRegistry = { ...this.contextMenuRegistry };
|
|
111
|
+
|
|
112
|
+
return registry;
|
|
113
|
+
}
|
|
114
|
+
|
|
88
115
|
/**
|
|
89
116
|
* Creates an internal registry name by camelizing the provided registry name and appending "ConextmenuRegistry" to it.
|
|
90
117
|
*
|
|
@@ -7,6 +7,7 @@ import { renderCompleted, waitForInsertedAndSized } from '@fleetbase/ember-ui/ut
|
|
|
7
7
|
import { Control as RoutingControl } from '@fleetbase/leaflet-routing-machine';
|
|
8
8
|
import { getLayerById, findLayer, flyToLayer } from '../utils/leaflet';
|
|
9
9
|
import isUuid from '@fleetbase/ember-core/utils/is-uuid';
|
|
10
|
+
import ensureLeafletPluginsReady from '../utils/leaflet-plugin-loader';
|
|
10
11
|
|
|
11
12
|
export default class LeafletMapManagerService extends Service {
|
|
12
13
|
@service leafletRoutingControl;
|
|
@@ -92,6 +93,7 @@ export default class LeafletMapManagerService extends Service {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
async ensureInteractive({ timeoutMs = 8000 } = {}) {
|
|
96
|
+
await ensureLeafletPluginsReady({ timeoutMs });
|
|
95
97
|
await this.whenMapLoaded({ timeoutMs });
|
|
96
98
|
|
|
97
99
|
// always ask Leaflet for the live container
|