@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
|
@@ -8,11 +8,15 @@ 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
|
+
import ensureLeafletPluginsReady, { hasLeafletPluginsReady } from '../../utils/leaflet-plugin-loader';
|
|
14
15
|
|
|
15
16
|
export default class MapLeafletLiveMapComponent extends Component {
|
|
17
|
+
liveViewportReloadDebounceMs = 300;
|
|
18
|
+
liveViewportResourceLimit = 500;
|
|
19
|
+
|
|
16
20
|
@service mapManager;
|
|
17
21
|
@service mapSettings;
|
|
18
22
|
@service leafletMapManager;
|
|
@@ -50,6 +54,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
50
54
|
@tracked drivers = [];
|
|
51
55
|
@tracked vehicles = [];
|
|
52
56
|
@tracked places = [];
|
|
57
|
+
@tracked leafletPluginsReady = hasLeafletPluginsReady();
|
|
53
58
|
_viewportReloadLocks = new Set();
|
|
54
59
|
|
|
55
60
|
constructor() {
|
|
@@ -76,6 +81,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
76
81
|
this._geofenceExitedHandler = this.#handleGeofenceExited.bind(this);
|
|
77
82
|
this.universe.on('fleet-ops.geofence.entered', this._geofenceEnteredHandler);
|
|
78
83
|
this.universe.on('fleet-ops.geofence.exited', this._geofenceExitedHandler);
|
|
84
|
+
|
|
85
|
+
if (!this.shouldUseGoogleMaps && !this.leafletPluginsReady) {
|
|
86
|
+
this.prepareLeafletPlugins();
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
willDestroy() {
|
|
@@ -98,6 +107,11 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
98
107
|
this.universe.off('fleet-ops.geofence.exited', this._geofenceExitedHandler);
|
|
99
108
|
this._geofenceExitedHandler = null;
|
|
100
109
|
}
|
|
110
|
+
if (this._viewportReloadHandler && this.map && typeof this.map.off === 'function') {
|
|
111
|
+
this.map.off('moveend', this._viewportReloadHandler);
|
|
112
|
+
this.map.off('zoomend', this._viewportReloadHandler);
|
|
113
|
+
this._viewportReloadHandler = null;
|
|
114
|
+
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
@action didLoad({ target: map }) {
|
|
@@ -106,8 +120,9 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
106
120
|
this.trigger('onLoad', ...arguments);
|
|
107
121
|
this.load.perform();
|
|
108
122
|
// Listen for map move/zoom events to trigger viewport-based resource reload
|
|
109
|
-
|
|
110
|
-
map.on('
|
|
123
|
+
this._viewportReloadHandler = () => this.reloadResourcesInViewport.perform();
|
|
124
|
+
map.on('moveend', this._viewportReloadHandler);
|
|
125
|
+
map.on('zoomend', this._viewportReloadHandler);
|
|
111
126
|
}
|
|
112
127
|
|
|
113
128
|
/**
|
|
@@ -211,13 +226,27 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
211
226
|
this.#createZoneContextMenu(zone, layer);
|
|
212
227
|
}
|
|
213
228
|
|
|
229
|
+
@action rebuildMapContextMenu() {
|
|
230
|
+
if (!this.map) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.#createMapContextMenu(this.map);
|
|
235
|
+
this.#refreshResourceContextMenus();
|
|
236
|
+
this.#pruneResourceContextMenus();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@action syncServiceAreaContextMenus() {
|
|
240
|
+
this.rebuildMapContextMenu();
|
|
241
|
+
}
|
|
242
|
+
|
|
214
243
|
/** load resources and wait for stuff here and trigger map ready **/
|
|
215
244
|
@task *load() {
|
|
216
245
|
try {
|
|
217
246
|
// Get initial map bounds for spatial filtering (provider-agnostic)
|
|
218
247
|
const mapBounds = this.mapManager.getBounds?.() ?? (this.map ? this.map.getBounds() : null);
|
|
219
248
|
const bounds = this.#serializeBoundsForRequest(mapBounds);
|
|
220
|
-
const params = bounds
|
|
249
|
+
const params = this.#liveViewportRequestParams(bounds);
|
|
221
250
|
|
|
222
251
|
const data = yield all([
|
|
223
252
|
this.loadResource.perform('routes'),
|
|
@@ -242,12 +271,19 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
242
271
|
if (this.isViewportReloadSuspended) {
|
|
243
272
|
return;
|
|
244
273
|
}
|
|
274
|
+
|
|
275
|
+
yield timeout(this.liveViewportReloadDebounceMs);
|
|
276
|
+
|
|
277
|
+
if (this.isViewportReloadSuspended) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
245
281
|
// Get current map bounds (provider-agnostic)
|
|
246
282
|
const rawBounds = this.mapManager.getBounds?.() ?? this.map?.getBounds();
|
|
247
283
|
if (!rawBounds) return;
|
|
248
284
|
const bounds = this.#serializeBoundsForRequest(rawBounds);
|
|
249
285
|
if (!bounds) return;
|
|
250
|
-
const params =
|
|
286
|
+
const params = this.#liveViewportRequestParams(bounds);
|
|
251
287
|
|
|
252
288
|
// Reload spatially-filtered resources (drivers, vehicles, places)
|
|
253
289
|
// Orders, routes, and service-areas are not spatially filtered
|
|
@@ -299,6 +335,17 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
299
335
|
return this.mapSettings.isGoogleMaps;
|
|
300
336
|
}
|
|
301
337
|
|
|
338
|
+
async prepareLeafletPlugins() {
|
|
339
|
+
try {
|
|
340
|
+
await ensureLeafletPluginsReady();
|
|
341
|
+
if (!this.isDestroying && !this.isDestroyed) {
|
|
342
|
+
this.leafletPluginsReady = true;
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
debug('Failed to prepare Leaflet plugins: ' + error.message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
302
349
|
/**
|
|
303
350
|
* Get valid zoom level for map initialization
|
|
304
351
|
* @returns {number} Valid zoom level between 1-20
|
|
@@ -445,6 +492,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
445
492
|
return null;
|
|
446
493
|
}
|
|
447
494
|
|
|
495
|
+
#liveViewportRequestParams(bounds) {
|
|
496
|
+
return bounds ? { bounds, limit: this.liveViewportResourceLimit } : { limit: this.liveViewportResourceLimit };
|
|
497
|
+
}
|
|
498
|
+
|
|
448
499
|
#setResourceLayer(model, layer, options = {}) {
|
|
449
500
|
const { hidden = false } = options;
|
|
450
501
|
const type = getModelName(model);
|
|
@@ -462,6 +513,7 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
462
513
|
}
|
|
463
514
|
|
|
464
515
|
#createMapContextMenu(map) {
|
|
516
|
+
const serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []);
|
|
465
517
|
const items = [
|
|
466
518
|
{
|
|
467
519
|
text: this.intl.t('live-map.show-coordinates'),
|
|
@@ -494,10 +546,10 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
494
546
|
callback: () => this.geofence.createServiceArea(),
|
|
495
547
|
index: 5,
|
|
496
548
|
},
|
|
497
|
-
|
|
498
|
-
...
|
|
549
|
+
serviceAreas.length ? { separator: true } : null,
|
|
550
|
+
...serviceAreas.map((serviceArea, i) => {
|
|
499
551
|
return {
|
|
500
|
-
text: this.intl.t('live-map.focus-service', { serviceName: serviceArea
|
|
552
|
+
text: this.intl.t('live-map.focus-service', { serviceName: this.#resourceDisplayName(serviceArea) }),
|
|
501
553
|
callback: () => this.geofence.focusServiceArea(serviceArea),
|
|
502
554
|
index: 6 + i,
|
|
503
555
|
};
|
|
@@ -511,71 +563,200 @@ export default class MapLeafletLiveMapComponent extends Component {
|
|
|
511
563
|
}
|
|
512
564
|
|
|
513
565
|
#createZoneContextMenu(zone, layer) {
|
|
566
|
+
const zoneName = this.#resourceDisplayName(zone);
|
|
514
567
|
let items = [
|
|
515
568
|
{
|
|
516
569
|
separator: true,
|
|
517
570
|
},
|
|
518
571
|
{
|
|
519
|
-
text: this.intl.t('live-map.edit-zone', { zoneName
|
|
572
|
+
text: this.intl.t('live-map.edit-zone', { zoneName }),
|
|
520
573
|
callback: () => this.zoneActions.modal.edit(zone),
|
|
521
574
|
},
|
|
522
575
|
{
|
|
523
|
-
text: this.intl.t('live-map.delete-zone', { zoneName
|
|
524
|
-
callback: () => this
|
|
576
|
+
text: this.intl.t('live-map.delete-zone', { zoneName }),
|
|
577
|
+
callback: () => this.#deleteZone(zone),
|
|
525
578
|
},
|
|
526
579
|
];
|
|
527
580
|
|
|
528
581
|
if (!this.mapManager.isGoogleMaps) {
|
|
529
582
|
items.splice(2, 0, {
|
|
530
|
-
text: this.intl.t('live-map.edit-boundaries', { resource:
|
|
583
|
+
text: this.intl.t('live-map.edit-boundaries', { resource: zoneName }),
|
|
531
584
|
callback: () => this.geofence.editZone(zone),
|
|
532
585
|
});
|
|
533
586
|
}
|
|
534
587
|
|
|
535
588
|
// create contextmenu registry
|
|
536
|
-
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(
|
|
589
|
+
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(this.#resourceContextKey('zone', zone), layer, items, { zone });
|
|
537
590
|
this.universe.trigger('fleet-ops:contextmenu:zone:created', contextmenuRegistry, this.leafletContextmenuManager);
|
|
538
591
|
|
|
539
592
|
return contextmenuRegistry;
|
|
540
593
|
}
|
|
541
594
|
|
|
542
595
|
#createServiceAreaContextMenu(serviceArea, layer) {
|
|
596
|
+
const serviceName = this.#resourceDisplayName(serviceArea);
|
|
543
597
|
let items = [
|
|
544
598
|
{
|
|
545
599
|
separator: true,
|
|
546
600
|
},
|
|
547
601
|
{
|
|
548
|
-
text: this.intl.t('live-map.blur-service', { serviceName
|
|
602
|
+
text: this.intl.t('live-map.blur-service', { serviceName }),
|
|
549
603
|
callback: () => this.geofence.blurServiceArea(serviceArea),
|
|
550
604
|
},
|
|
551
605
|
{
|
|
552
|
-
text: this.intl.t('live-map.create-zone', { serviceName
|
|
606
|
+
text: this.intl.t('live-map.create-zone', { serviceName }),
|
|
553
607
|
callback: () => this.geofence.createZone(serviceArea),
|
|
554
608
|
},
|
|
555
609
|
{
|
|
556
|
-
text: this.intl.t('live-map.edit-service', { serviceName
|
|
610
|
+
text: this.intl.t('live-map.edit-service', { serviceName }),
|
|
557
611
|
callback: () => this.serviceAreaActions.modal.edit(serviceArea),
|
|
558
612
|
},
|
|
559
613
|
{
|
|
560
|
-
text: this.intl.t('live-map.delete-service', { serviceName
|
|
561
|
-
callback: () => this
|
|
614
|
+
text: this.intl.t('live-map.delete-service', { serviceName }),
|
|
615
|
+
callback: () => this.#deleteServiceArea(serviceArea),
|
|
562
616
|
},
|
|
563
617
|
];
|
|
564
618
|
|
|
565
619
|
if (!this.mapManager.isGoogleMaps) {
|
|
566
620
|
items.splice(4, 0, {
|
|
567
|
-
text: this.intl.t('live-map.edit-boundaries', { resource:
|
|
621
|
+
text: this.intl.t('live-map.edit-boundaries', { resource: serviceName }),
|
|
568
622
|
callback: () => this.geofence.editServiceArea(serviceArea),
|
|
569
623
|
});
|
|
570
624
|
}
|
|
571
625
|
|
|
572
626
|
// create contextmenu registry
|
|
573
|
-
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(
|
|
627
|
+
const contextmenuRegistry = this.leafletContextmenuManager.createContextMenu(this.#resourceContextKey('service-area', serviceArea), layer, items, { serviceArea });
|
|
574
628
|
this.universe.trigger('fleet-ops:contextmenu:service-area:created', contextmenuRegistry, this.leafletContextmenuManager);
|
|
575
629
|
|
|
576
630
|
return contextmenuRegistry;
|
|
577
631
|
}
|
|
578
632
|
|
|
633
|
+
#resourceContextKey(type, resource) {
|
|
634
|
+
return `${type}:${resource?.public_id ?? resource?.id}`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
#resourceDisplayName(resource) {
|
|
638
|
+
return resource?.get?.('name') || resource?.name || resource?.display_name || resource?.public_id || resource?.id || '';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
#deleteServiceArea(serviceArea) {
|
|
642
|
+
const zones = Array.from(serviceArea?.zones ?? []);
|
|
643
|
+
|
|
644
|
+
return this.serviceAreaActions.delete(
|
|
645
|
+
serviceArea,
|
|
646
|
+
{},
|
|
647
|
+
{
|
|
648
|
+
callback: () => {
|
|
649
|
+
this.#removeServiceAreaFromMapState(serviceArea, zones);
|
|
650
|
+
this.rebuildMapContextMenu();
|
|
651
|
+
},
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
#deleteZone(zone) {
|
|
657
|
+
return this.zoneActions.delete(
|
|
658
|
+
zone,
|
|
659
|
+
{},
|
|
660
|
+
{
|
|
661
|
+
callback: () => {
|
|
662
|
+
this.#removeZoneFromMapState(zone);
|
|
663
|
+
this.rebuildMapContextMenu();
|
|
664
|
+
},
|
|
665
|
+
}
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
#removeServiceAreaFromMapState(serviceArea, zones = []) {
|
|
670
|
+
const serviceAreaId = serviceArea?.id;
|
|
671
|
+
const serviceAreaKey = this.#resourceContextKey('service-area', serviceArea);
|
|
672
|
+
|
|
673
|
+
zones.forEach((zone) => this.#removeZoneFromMapState(zone));
|
|
674
|
+
this.#removeOverlay(serviceArea);
|
|
675
|
+
this.leafletContextmenuManager.removeContextMenu(serviceAreaKey);
|
|
676
|
+
|
|
677
|
+
this.serviceAreaActions.serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []).filter((candidate) => candidate?.id !== serviceAreaId);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
#removeZoneFromMapState(zone) {
|
|
681
|
+
const zoneId = zone?.id;
|
|
682
|
+
const zoneKey = this.#resourceContextKey('zone', zone);
|
|
683
|
+
|
|
684
|
+
this.#removeOverlay(zone);
|
|
685
|
+
this.leafletContextmenuManager.removeContextMenu(zoneKey);
|
|
686
|
+
|
|
687
|
+
for (const serviceArea of Array.from(this.serviceAreaActions.serviceAreas ?? [])) {
|
|
688
|
+
const zones = serviceArea?.zones;
|
|
689
|
+
if (!zones) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const currentZones = Array.from(zones);
|
|
694
|
+
const nextZones = currentZones.filter((candidate) => candidate?.id !== zoneId);
|
|
695
|
+
if (nextZones.length !== currentZones.length) {
|
|
696
|
+
if (typeof zones.removeObject === 'function') {
|
|
697
|
+
zones.removeObject(zone);
|
|
698
|
+
} else {
|
|
699
|
+
serviceArea.set?.('zones', nextZones);
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
this.serviceAreaActions.serviceAreas = Array.from(this.serviceAreaActions.serviceAreas ?? []);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
#removeOverlay(resource) {
|
|
709
|
+
if (!resource?.id) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
this.mapManager.removeOverlay?.(resource.id);
|
|
714
|
+
|
|
715
|
+
const layer = resource?.leafletLayer;
|
|
716
|
+
if (layer) {
|
|
717
|
+
this.mapManager.removeLayer?.(layer);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
#refreshResourceContextMenus() {
|
|
722
|
+
for (const serviceArea of Array.from(this.serviceAreaActions.serviceAreas ?? [])) {
|
|
723
|
+
const serviceAreaLayer = this.mapManager.getOverlay(serviceArea?.id) ?? serviceArea?.leafletLayer;
|
|
724
|
+
if (serviceAreaLayer) {
|
|
725
|
+
this.#createServiceAreaContextMenu(serviceArea, serviceAreaLayer);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
for (const zone of Array.from(serviceArea?.zones ?? [])) {
|
|
729
|
+
const zoneLayer = this.mapManager.getOverlay(zone?.id) ?? zone?.leafletLayer;
|
|
730
|
+
if (zoneLayer) {
|
|
731
|
+
this.#createZoneContextMenu(zone, zoneLayer);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
#pruneResourceContextMenus() {
|
|
738
|
+
const activeServiceAreaKeys = new Set();
|
|
739
|
+
const activeZoneKeys = new Set();
|
|
740
|
+
|
|
741
|
+
for (const serviceArea of Array.from(this.serviceAreaActions.serviceAreas ?? [])) {
|
|
742
|
+
activeServiceAreaKeys.add(this.#resourceContextKey('service-area', serviceArea));
|
|
743
|
+
|
|
744
|
+
for (const zone of Array.from(serviceArea?.zones ?? [])) {
|
|
745
|
+
activeZoneKeys.add(this.#resourceContextKey('zone', zone));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
for (const registry of Object.values(this.leafletContextmenuManager.contextMenuRegistry ?? {})) {
|
|
750
|
+
if (registry?.serviceArea && !activeServiceAreaKeys.has(this.#resourceContextKey('service-area', registry.serviceArea))) {
|
|
751
|
+
this.leafletContextmenuManager.removeContextMenu(this.#resourceContextKey('service-area', registry.serviceArea));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (registry?.zone && !activeZoneKeys.has(this.#resourceContextKey('zone', registry.zone))) {
|
|
755
|
+
this.leafletContextmenuManager.removeContextMenu(this.#resourceContextKey('zone', registry.zone));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
579
760
|
#createDriverContextMenu(driver, layer) {
|
|
580
761
|
let items = [
|
|
581
762
|
{
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
<div class="next-dd-menu-seperator"></div>
|
|
45
45
|
<div class="px-1">
|
|
46
46
|
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.orderActions.bulkCancel this.overlay.selectedOrders)}}>{{t
|
|
47
|
-
"
|
|
47
|
+
"order-list-overlay.cancel"
|
|
48
48
|
}}</a>
|
|
49
49
|
<a href="javascript:;" class="next-dd-item text-danger" {{on "click" (dropdown-fn dd this.orderActions.bulkDelete this.overlay.selectedOrders)}}>{{t
|
|
50
|
-
"
|
|
50
|
+
"order-list-overlay.delete"
|
|
51
51
|
}}</a>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
<div class="next-dd-menu-seperator"></div>
|
|
71
71
|
<div class="px-1">
|
|
72
72
|
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd (transition-to "operations.orders.index.new"))}}>{{t
|
|
73
|
-
"
|
|
73
|
+
"order-list-overlay.create-order"
|
|
74
74
|
}}</a>
|
|
75
75
|
<a href="javascript:;" class="next-dd-item" {{on "click" (dropdown-fn dd this.fleetActions.modal.create)}}>{{t
|
|
76
|
-
"
|
|
76
|
+
"order-list-overlay.create-fleet"
|
|
77
77
|
}}</a>
|
|
78
78
|
</div>
|
|
79
79
|
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
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-yellow-200 bg-yellow-50 p-3 text-sm text-yellow-900 dark:border-yellow-700 dark:bg-yellow-900 dark:text-yellow-100">
|
|
4
|
+
<div class="mb-1 font-semibold">Create a company customer account</div>
|
|
5
|
+
<div>
|
|
6
|
+
This action will create a vendor, assign
|
|
7
|
+
{{this.customer.name}}
|
|
8
|
+
as admin personnel, and migrate any customer-owned portal history to the vendor account.
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<InputGroup @name="Primary Personnel" @wrapperClass="mb-0i">
|
|
13
|
+
<div class="flex items-start rounded-md border border-gray-200 bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-800">
|
|
14
|
+
<Image src={{this.customer.photo_url}} @fallbackSrc={{config "defaultValues.contactImage"}} alt={{this.customer.name}} class="mr-2 h-6 w-6 rounded-md" />
|
|
15
|
+
<div class="min-w-0">
|
|
16
|
+
<div class="leading-4 text-sm truncate font-semibold dark:text-gray-100">{{this.customer.name}}</div>
|
|
17
|
+
<div class="truncate text-sm text-gray-500 dark:text-gray-400">{{n-a this.customer.email}}</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</InputGroup>
|
|
21
|
+
|
|
22
|
+
<div class="mt-4 grid grid-cols-1 gap-3 text-xs lg:grid-cols-2">
|
|
23
|
+
<InputGroup @name="Vendor Name" @value={{this.vendorName}} @placeholder="Company name" @wrapperClass="mb-0i" />
|
|
24
|
+
<InputGroup @name="Email" @type="email" @value={{this.vendorEmail}} @placeholder="billing@example.com" @wrapperClass="mb-0i" />
|
|
25
|
+
<InputGroup @name="Phone" @wrapperClass="mb-0i">
|
|
26
|
+
<PhoneInput @value={{this.vendorPhone}} @onInput={{fn (mut this.vendorPhone)}} class="form-input w-full" />
|
|
27
|
+
</InputGroup>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</Modal::Default>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { tracked } from '@glimmer/tracking';
|
|
4
|
+
|
|
5
|
+
export default class ModalsConvertCustomerToVendorComponent extends Component {
|
|
6
|
+
@service fetch;
|
|
7
|
+
@service notifications;
|
|
8
|
+
|
|
9
|
+
@tracked customer;
|
|
10
|
+
@tracked vendorName;
|
|
11
|
+
@tracked vendorEmail;
|
|
12
|
+
@tracked vendorPhone;
|
|
13
|
+
@tracked options = {};
|
|
14
|
+
|
|
15
|
+
constructor(owner, { options }) {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.customer = options.customer;
|
|
18
|
+
this.vendorName = this.customer?.name;
|
|
19
|
+
this.vendorEmail = this.customer?.email;
|
|
20
|
+
this.vendorPhone = this.customer?.phone;
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.setupOptions();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setupOptions() {
|
|
26
|
+
this.options.title = 'Convert Contact to Vendor';
|
|
27
|
+
this.options.acceptButtonText = 'Create Vendor';
|
|
28
|
+
this.options.confirm = async (modal) => {
|
|
29
|
+
modal.startLoading();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await this.fetch.post(
|
|
33
|
+
`contacts/${this.customer.id}/convert-to-vendor`,
|
|
34
|
+
{
|
|
35
|
+
name: this.vendorName,
|
|
36
|
+
email: this.vendorEmail,
|
|
37
|
+
phone: this.vendorPhone,
|
|
38
|
+
},
|
|
39
|
+
{ namespace: 'int/v1' }
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
this.notifications.success('Customer converted to vendor account.');
|
|
43
|
+
|
|
44
|
+
if (typeof this.options.onConverted === 'function') {
|
|
45
|
+
this.options.onConverted(response.vendor);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
modal.done();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.notifications.serverError(error);
|
|
51
|
+
modal.stopLoading();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
|
2
|
+
<div class="modal-body-container">
|
|
3
|
+
<InputGroup @name="Assignee" @helpText="Choose the operator responsible for following up on this issue.">
|
|
4
|
+
<ModelSelect
|
|
5
|
+
@modelName="user"
|
|
6
|
+
@selectedModel={{this.selectedAssignee}}
|
|
7
|
+
@placeholder="Select assignee"
|
|
8
|
+
@triggerClass="form-select form-input"
|
|
9
|
+
@infiniteScroll={{false}}
|
|
10
|
+
@renderInPlace={{true}}
|
|
11
|
+
@query={{hash is_not_customer=true}}
|
|
12
|
+
@allowClear={{true}}
|
|
13
|
+
@onChange={{fn (mut this.selectedAssignee)}}
|
|
14
|
+
as |model|
|
|
15
|
+
>
|
|
16
|
+
{{model.name}}
|
|
17
|
+
</ModelSelect>
|
|
18
|
+
</InputGroup>
|
|
19
|
+
</div>
|
|
20
|
+
</Modal::Default>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
|
|
5
|
+
export default class ModalsIssueAssignComponent extends Component {
|
|
6
|
+
@service notifications;
|
|
7
|
+
|
|
8
|
+
@tracked options = {};
|
|
9
|
+
@tracked issue;
|
|
10
|
+
@tracked selectedAssignee;
|
|
11
|
+
|
|
12
|
+
constructor(owner, { options }) {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.issue = options.issue;
|
|
16
|
+
this.selectedAssignee = this.issue?.assignee;
|
|
17
|
+
this.setupOptions();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setupOptions() {
|
|
21
|
+
this.options.title = 'Assign Issue';
|
|
22
|
+
this.options.acceptButtonText = 'Assign Issue';
|
|
23
|
+
this.options.acceptButtonIcon = 'user-check';
|
|
24
|
+
this.options.confirm = async (modal) => {
|
|
25
|
+
modal.startLoading();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (this.selectedAssignee?.type === 'customer') {
|
|
29
|
+
this.notifications.warning('Customers cannot be assigned to issues.');
|
|
30
|
+
modal.stopLoading();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.issue.assignee = this.selectedAssignee;
|
|
35
|
+
await this.issue.save();
|
|
36
|
+
this.notifications.success('Issue assignment updated.');
|
|
37
|
+
|
|
38
|
+
if (typeof this.options.onSaved === 'function') {
|
|
39
|
+
await this.options.onSaved(this.issue);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
modal.done();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.notifications.serverError(error);
|
|
45
|
+
modal.stopLoading();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
|
2
|
+
<div class="modal-body-container">
|
|
3
|
+
<div class="issue-close-modal-summary">
|
|
4
|
+
{{#if this.vehicleLabel}}
|
|
5
|
+
<div>
|
|
6
|
+
<span>Vehicle</span>
|
|
7
|
+
<strong>{{this.vehicleLabel}}</strong>
|
|
8
|
+
</div>
|
|
9
|
+
{{/if}}
|
|
10
|
+
{{#if this.orderLabel}}
|
|
11
|
+
<div>
|
|
12
|
+
<span>Order</span>
|
|
13
|
+
<strong>{{this.orderLabel}}</strong>
|
|
14
|
+
</div>
|
|
15
|
+
{{/if}}
|
|
16
|
+
<div>
|
|
17
|
+
<span>Summary</span>
|
|
18
|
+
<strong>{{n-a this.issue.title}}</strong>
|
|
19
|
+
</div>
|
|
20
|
+
<div>
|
|
21
|
+
<span>Description</span>
|
|
22
|
+
<strong>{{n-a this.issue.report "No report was provided."}}</strong>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<InputGroup @name="Close Note" @helpText="Describe why this issue is being closed.">
|
|
27
|
+
<Textarea @value={{this.note}} class="form-input w-full" rows={{5}} placeholder="Resolution note" />
|
|
28
|
+
</InputGroup>
|
|
29
|
+
</div>
|
|
30
|
+
</Modal::Default>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
|
|
5
|
+
export default class ModalsIssueCloseComponent extends Component {
|
|
6
|
+
@service currentUser;
|
|
7
|
+
@service notifications;
|
|
8
|
+
|
|
9
|
+
@tracked options = {};
|
|
10
|
+
@tracked issue;
|
|
11
|
+
@tracked note;
|
|
12
|
+
|
|
13
|
+
constructor(owner, { options }) {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.issue = options.issue;
|
|
17
|
+
this.setupOptions();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get orderLabel() {
|
|
21
|
+
return this.issue?.order?.tracking || this.issue?.order?.public_id;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get vehicleLabel() {
|
|
25
|
+
return this.issue?.vehicle?.displayName || this.issue?.vehicle_name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setupOptions() {
|
|
29
|
+
this.options.title = `Close ${this.issue?.public_id || 'Issue'}`;
|
|
30
|
+
this.options.acceptButtonText = 'Close Issue';
|
|
31
|
+
this.options.acceptButtonIcon = 'circle-check';
|
|
32
|
+
this.options.acceptButtonScheme = 'success';
|
|
33
|
+
this.options.confirm = async (modal) => {
|
|
34
|
+
if (!this.note) {
|
|
35
|
+
this.notifications.warning('Add a close note before closing the issue.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
modal.startLoading();
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const closedAt = new Date().toISOString();
|
|
43
|
+
const user = this.currentUser.user;
|
|
44
|
+
const meta = this.issue.meta ?? {};
|
|
45
|
+
|
|
46
|
+
this.issue.status = 'closed';
|
|
47
|
+
this.issue.resolved_at = new Date();
|
|
48
|
+
this.issue.meta = {
|
|
49
|
+
...meta,
|
|
50
|
+
resolution: {
|
|
51
|
+
...(meta.resolution ?? {}),
|
|
52
|
+
note: this.note,
|
|
53
|
+
closed_at: closedAt,
|
|
54
|
+
closed_by_uuid: user?.id,
|
|
55
|
+
closed_by_name: user?.name,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
await this.issue.save();
|
|
60
|
+
this.notifications.success('Issue closed.');
|
|
61
|
+
|
|
62
|
+
if (typeof this.options.onSaved === 'function') {
|
|
63
|
+
await this.options.onSaved(this.issue);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
modal.done();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.notifications.serverError(error);
|
|
69
|
+
modal.stopLoading();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|