@fleetbase/fleetops-engine 0.6.50 → 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/details.hbs +1 -2
- package/addon/components/customer/form.hbs +63 -51
- package/addon/components/customer/form.js +27 -1
- package/addon/components/customer/orders.hbs +48 -0
- package/addon/components/customer/orders.js +67 -5
- 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/leaflet-live-map.hbs +2 -2
- package/addon/components/map/leaflet-live-map.js +17 -0
- 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/reset-customer-credentials.js +12 -6
- package/addon/components/modals/update-order-activity.hbs +51 -17
- package/addon/components/modals/update-order-activity.js +59 -5
- package/addon/components/order/details/activity.js +2 -2
- package/addon/components/order/details/proof.hbs +40 -38
- package/addon/components/order/details/proof.js +31 -4
- package/addon/components/order/details.hbs +5 -5
- package/addon/components/order/details.js +19 -1
- package/addon/components/order/header.hbs +1 -1
- package/addon/components/route-list.hbs +0 -12
- package/addon/components/service-rate/details.hbs +1 -1
- package/addon/components/service-rate/form.hbs +1 -1
- package/addon/components/service-rate/form.js +4 -0
- 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/vendors/index/details.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +11 -3
- package/addon/controllers/operations/orders/index.js +1 -1
- package/addon/extension.js +19 -12
- package/addon/helpers/format-duration.js +1 -38
- 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.js +1 -0
- package/addon/services/contact-actions.js +231 -1
- package/addon/services/getting-started.js +44 -0
- package/addon/services/issue-actions.js +71 -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/styles/fleetops-engine.css +1036 -0
- 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/leaflet-draw-namespace-guard.js +22 -0
- package/addon/utils/leaflet-plugin-loader.js +219 -0
- 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/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_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/mail/customer-credentials.blade.php +16 -13
- package/server/src/Expansions/UserFilterExpansion.php +28 -0
- package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -25
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +135 -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/OrderController.php +172 -97
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
- package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
- package/server/src/Http/Requests/UpdateIssueRequest.php +4 -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/Mail/CustomerCredentialsMail.php +5 -6
- package/server/src/Models/Contact.php +30 -51
- package/server/src/Models/Issue.php +47 -6
- package/server/src/Models/Order.php +33 -6
- package/server/src/Models/ServiceArea.php +1 -1
- package/server/src/Models/VendorPersonnel.php +10 -1
- package/server/src/Models/Zone.php +1 -1
- package/server/src/Observers/PurchaseRateObserver.php +20 -0
- package/server/src/Observers/ZoneObserver.php +56 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +1 -0
- package/server/src/Support/Analytics/TopDrivers.php +9 -1
- package/server/src/Support/GettingStarted.php +144 -0
- package/server/src/Support/PlaceSearch.php +282 -0
- package/server/src/Support/ResolvesOrderServiceStops.php +22 -12
- package/server/src/routes.php +33 -1
- package/server/tests/AnalyticsRoutesTest.php +8 -0
- package/server/tests/CustomerContactIdentitySafetyTest.php +41 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +2 -2
- package/server/tests/GettingStartedStatusTest.php +21 -0
- package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +44 -0
- package/translations/ar-ae.yml +9 -0
- package/translations/bg-bg.yaml +9 -0
- package/translations/en-us.yaml +11 -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
|
@@ -4,16 +4,62 @@ import { inject as service } from '@ember/service';
|
|
|
4
4
|
|
|
5
5
|
export default class ManagementIssuesIndexDetailsController extends Controller {
|
|
6
6
|
@service hostRouter;
|
|
7
|
+
@service issueActions;
|
|
8
|
+
|
|
7
9
|
@tracked tabs = [
|
|
8
10
|
{
|
|
9
11
|
route: 'management.issues.index.details.index',
|
|
10
12
|
label: 'Overview',
|
|
11
13
|
},
|
|
12
14
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
get isClosed() {
|
|
17
|
+
return ['closed', 'resolved', 'completed'].includes(this.model?.status);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get actionButtons() {
|
|
21
|
+
const workflowItems = [
|
|
22
|
+
{
|
|
23
|
+
label: 'Change Status',
|
|
24
|
+
icon: 'arrows-rotate',
|
|
25
|
+
fn: () => this.issueActions.openStatusModal(this.model, { onSaved: () => this.refreshIssue() }),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Assign Issue',
|
|
29
|
+
icon: 'user-check',
|
|
30
|
+
fn: () => this.issueActions.openAssignModal(this.model, { onSaved: () => this.refreshIssue() }),
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
if (this.isClosed) {
|
|
35
|
+
workflowItems.push({
|
|
36
|
+
label: 'Re-open Issue',
|
|
37
|
+
icon: 'rotate-left',
|
|
38
|
+
fn: () => this.issueActions.confirmReopenIssue(this.model, { onSaved: () => this.refreshIssue() }),
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
workflowItems.push({
|
|
42
|
+
label: 'Close Issue',
|
|
43
|
+
icon: 'circle-check',
|
|
44
|
+
class: 'text-green-600 dark:text-green-400',
|
|
45
|
+
fn: () => this.issueActions.openCloseIssueModal(this.model, { onSaved: () => this.refreshIssue() }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
icon: 'pencil',
|
|
52
|
+
fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.issues.index.edit', this.model),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
icon: 'ellipsis',
|
|
56
|
+
type: 'default',
|
|
57
|
+
items: workflowItems,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
refreshIssue() {
|
|
63
|
+
return this.model?.reload?.();
|
|
64
|
+
}
|
|
19
65
|
}
|
|
@@ -9,6 +9,10 @@ export default class ManagementVendorsIndexDetailsController extends Controller
|
|
|
9
9
|
route: 'management.vendors.index.details.index',
|
|
10
10
|
label: 'Overview',
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
route: 'management.vendors.index.details.personnel',
|
|
14
|
+
label: 'Personnel',
|
|
15
|
+
},
|
|
12
16
|
];
|
|
13
17
|
@tracked actionButtons = [
|
|
14
18
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Controller, { inject as controller } from '@ember/controller';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action } from '@ember/object';
|
|
3
4
|
import { inject as service } from '@ember/service';
|
|
4
5
|
import { isArray } from '@ember/array';
|
|
5
6
|
import { task } from 'ember-concurrency';
|
|
@@ -19,6 +20,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
19
20
|
@tracked routingControl;
|
|
20
21
|
@tracked routingCompleted = false;
|
|
21
22
|
@tracked realtimeOrderPublicId = null;
|
|
23
|
+
@tracked proofReloadToken = 0;
|
|
22
24
|
|
|
23
25
|
get routePoints() {
|
|
24
26
|
return buildRoutePointsFromPayload(this.model?.payload);
|
|
@@ -97,9 +99,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
97
99
|
disabled: this.model.status === 'canceled',
|
|
98
100
|
fn: () =>
|
|
99
101
|
this.orderActions.updateActivity(this.model, {
|
|
100
|
-
onFinish:
|
|
101
|
-
this.refresh.perform();
|
|
102
|
-
},
|
|
102
|
+
onFinish: this.handleActivityModalFinish,
|
|
103
103
|
}),
|
|
104
104
|
},
|
|
105
105
|
{
|
|
@@ -154,6 +154,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
|
|
|
154
154
|
];
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
@action handleActivityModalFinish(options) {
|
|
158
|
+
this.refresh.perform();
|
|
159
|
+
|
|
160
|
+
if (options?.proofCreated) {
|
|
161
|
+
this.proofReloadToken++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
157
165
|
@task *refresh() {
|
|
158
166
|
try {
|
|
159
167
|
yield this.hostRouter.refresh();
|
|
@@ -271,7 +271,7 @@ export default class OperationsOrdersIndexController extends Controller {
|
|
|
271
271
|
},
|
|
272
272
|
{
|
|
273
273
|
label: this.intl.t('column.transaction'),
|
|
274
|
-
cellComponent: 'table/cell/
|
|
274
|
+
cellComponent: 'table/cell/currency',
|
|
275
275
|
valuePath: 'transaction_amount',
|
|
276
276
|
resizable: true,
|
|
277
277
|
hidden: true,
|
package/addon/extension.js
CHANGED
|
@@ -117,6 +117,9 @@ export default {
|
|
|
117
117
|
// Create registries
|
|
118
118
|
this.createRegistries(registryService);
|
|
119
119
|
|
|
120
|
+
// // Register console home guidance
|
|
121
|
+
// this.registerHomeComponents(registryService);
|
|
122
|
+
|
|
120
123
|
// Setup customer portal
|
|
121
124
|
const isCustomerPortalInstalled = universe.extensionManager.isInstalled('@fleetbase/customer-portal-engine');
|
|
122
125
|
if (isCustomerPortalInstalled) {
|
|
@@ -216,13 +219,23 @@ export default {
|
|
|
216
219
|
category: 'Analytics',
|
|
217
220
|
default: false,
|
|
218
221
|
}),
|
|
222
|
+
new Widget({
|
|
223
|
+
id: 'fleet-ops-live-fleet-widget',
|
|
224
|
+
name: 'Live Fleet Map',
|
|
225
|
+
description: 'Real-time driver positions and active routes.',
|
|
226
|
+
icon: 'map-location-dot',
|
|
227
|
+
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/live-fleet'),
|
|
228
|
+
grid_options: { w: 8, h: 11, minW: 8, minH: 8 },
|
|
229
|
+
category: 'Maps',
|
|
230
|
+
default: true,
|
|
231
|
+
}),
|
|
219
232
|
new Widget({
|
|
220
233
|
id: 'fleet-ops-revenue-trend-widget',
|
|
221
234
|
name: 'Revenue Trend',
|
|
222
235
|
description: 'Revenue over time with period comparison.',
|
|
223
236
|
icon: 'chart-line',
|
|
224
237
|
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/revenue-trend'),
|
|
225
|
-
grid_options: { w:
|
|
238
|
+
grid_options: { w: 4, h: 11, minW: 4, minH: 8 },
|
|
226
239
|
category: 'Analytics',
|
|
227
240
|
default: true,
|
|
228
241
|
}),
|
|
@@ -254,7 +267,7 @@ export default {
|
|
|
254
267
|
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/top-drivers'),
|
|
255
268
|
grid_options: { w: 6, h: 6, minW: 5, minH: 5 },
|
|
256
269
|
category: 'Analytics',
|
|
257
|
-
default:
|
|
270
|
+
default: true,
|
|
258
271
|
}),
|
|
259
272
|
new Widget({
|
|
260
273
|
id: 'fleet-ops-fuel-efficiency-widget',
|
|
@@ -296,21 +309,15 @@ export default {
|
|
|
296
309
|
category: 'Analytics',
|
|
297
310
|
default: false,
|
|
298
311
|
}),
|
|
299
|
-
new Widget({
|
|
300
|
-
id: 'fleet-ops-live-fleet-widget',
|
|
301
|
-
name: 'Live Fleet Map',
|
|
302
|
-
description: 'Real-time driver positions and active routes.',
|
|
303
|
-
icon: 'map-location-dot',
|
|
304
|
-
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/live-fleet'),
|
|
305
|
-
grid_options: { w: 12, h: 10, minW: 8, minH: 8 },
|
|
306
|
-
category: 'Maps',
|
|
307
|
-
default: true,
|
|
308
|
-
}),
|
|
309
312
|
];
|
|
310
313
|
|
|
311
314
|
widgetService.registerWidgets('dashboard', widgets);
|
|
312
315
|
},
|
|
313
316
|
|
|
317
|
+
registerHomeComponents(registryService) {
|
|
318
|
+
registryService.registerRenderableComponent('console:home:before-dashboard', new ExtensionComponent('@fleetbase/fleetops-engine', 'home/getting-started-guidance'));
|
|
319
|
+
},
|
|
320
|
+
|
|
314
321
|
createRegistries(registryService) {
|
|
315
322
|
registryService.createRegistries([
|
|
316
323
|
'engine:fleet-ops',
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function formatDurationValue(seconds) {
|
|
4
|
-
const value = Number(seconds);
|
|
5
|
-
|
|
6
|
-
if (!Number.isFinite(value)) {
|
|
7
|
-
return '0s';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const totalSeconds = Math.max(0, Math.ceil(value));
|
|
11
|
-
const days = Math.floor(totalSeconds / 86400);
|
|
12
|
-
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
13
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
14
|
-
const remainingSeconds = totalSeconds % 60;
|
|
15
|
-
const parts = [];
|
|
16
|
-
|
|
17
|
-
if (days) {
|
|
18
|
-
parts.push(`${days}d`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (hours) {
|
|
22
|
-
parts.push(`${hours}h`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (!days && minutes) {
|
|
26
|
-
parts.push(`${minutes}m`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!days && !hours && parts.length < 2 && remainingSeconds) {
|
|
30
|
-
parts.push(`${remainingSeconds}s`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return parts.length ? parts.join(' ') : '0s';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default helper(function formatDuration([seconds]) {
|
|
37
|
-
return formatDurationValue(seconds);
|
|
38
|
-
});
|
|
1
|
+
export { default, formatDurationValue } from '@fleetbase/ember-ui/helpers/format-duration';
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { debug } from '@ember/debug';
|
|
2
|
+
import ensureLeafletPluginsReady from '../utils/leaflet-plugin-loader';
|
|
2
3
|
|
|
3
4
|
export function initialize() {
|
|
4
5
|
let waitForLeaflet = setInterval(() => {
|
|
5
6
|
let leafletLoaded = window.L !== undefined;
|
|
6
7
|
if (leafletLoaded) {
|
|
7
|
-
loadLeafletPlugins({
|
|
8
|
-
scripts: ['leaflet.contextmenu.js', 'leaflet.draw-src.js'],
|
|
9
|
-
stylesheets: ['leaflet.contextmenu.css', 'leaflet.draw.css'],
|
|
10
|
-
globalIndicatorKey: 'fleetopsLeafletPluginsLoaded',
|
|
11
|
-
});
|
|
12
8
|
clearInterval(waitForLeaflet);
|
|
9
|
+
ensureLeafletPluginsReady().catch((error) => {
|
|
10
|
+
debug(error.message);
|
|
11
|
+
});
|
|
13
12
|
}
|
|
14
13
|
}, 100);
|
|
15
14
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import TooltipLayer from 'ember-leaflet/components/tooltip-layer';
|
|
2
|
+
|
|
3
|
+
function hasMethod(object, methodName) {
|
|
4
|
+
return typeof object?.[methodName] === 'function';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function initialize() {
|
|
8
|
+
const prototype = TooltipLayer?.prototype;
|
|
9
|
+
if (!prototype || prototype.__fleetopsTooltipLayerPatched) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const addToContainer = prototype.addToContainer;
|
|
14
|
+
const removeFromContainer = prototype.removeFromContainer;
|
|
15
|
+
const removePopupListeners = prototype._removePopupListeners;
|
|
16
|
+
|
|
17
|
+
prototype.addToContainer = function () {
|
|
18
|
+
const parentLayer = this.args?.parent?._layer;
|
|
19
|
+
if (!this._layer || !hasMethod(parentLayer, 'bindTooltip')) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return addToContainer.call(this);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
prototype.removeFromContainer = function () {
|
|
27
|
+
const parentLayer = this.args?.parent?._layer;
|
|
28
|
+
if (!hasMethod(parentLayer, 'unbindTooltip')) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return removeFromContainer.call(this);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
prototype._removePopupListeners = function () {
|
|
36
|
+
const parentMap = this.args?.parent?._layer?._map;
|
|
37
|
+
if (!hasMethod(parentMap, 'removeEventListener')) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return removePopupListeners.call(this);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
Object.defineProperty(prototype, '__fleetopsTooltipLayerPatched', {
|
|
45
|
+
value: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
initialize,
|
|
51
|
+
};
|
|
@@ -28,6 +28,6 @@ export default class ManagementIssuesIndexDetailsRoute extends Route {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
model({ public_id }) {
|
|
31
|
-
return this.store.queryRecord('issue', { public_id, single: true, with: ['driver', 'vehicle', 'assignee', 'reporter'] });
|
|
31
|
+
return this.store.queryRecord('issue', { public_id, single: true, with: ['driver', 'vehicle', 'assignee', 'reporter', 'order', 'files'] });
|
|
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
|
}
|
|
@@ -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
|
+
}
|