@fleetbase/fleetops-engine 0.6.56 → 0.6.57

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.
Files changed (60) hide show
  1. package/addon/components/ai/create-order-preview.hbs +297 -0
  2. package/addon/components/ai/create-order-preview.js +362 -0
  3. package/addon/components/ai/route-preview-map.hbs +79 -0
  4. package/addon/components/ai/route-preview-map.js +527 -0
  5. package/addon/components/fleet/details.hbs +21 -8
  6. package/addon/components/fleet/details.js +55 -1
  7. package/addon/components/fleet/hierarchy-tree.hbs +137 -0
  8. package/addon/components/fleet/hierarchy-tree.js +528 -0
  9. package/addon/components/map/drawer/device-event-listing.js +1 -0
  10. package/addon/components/map/drawer/driver-listing.js +1 -0
  11. package/addon/components/map/drawer/place-listing.js +1 -0
  12. package/addon/components/map/drawer/vehicle-listing.js +1 -0
  13. package/addon/components/order/details.hbs +34 -1
  14. package/addon/components/order/form/details.hbs +4 -0
  15. package/addon/components/order/form.hbs +9 -1
  16. package/addon/components/vehicle/card.hbs +14 -2
  17. package/addon/components/vehicle/form.hbs +13 -1
  18. package/addon/controllers/settings/map.js +4 -0
  19. package/addon/extension.js +14 -0
  20. package/addon/routes/management/fleets/index/details.js +1 -1
  21. package/addon/services/map-adapter/google.js +56 -16
  22. package/addon/services/map-manager.js +12 -0
  23. package/addon/styles/fleetops-engine.css +487 -1
  24. package/addon/utils/map-drawer-dropdown-position.js +11 -13
  25. package/app/components/ai/create-order-preview.js +1 -0
  26. package/app/components/ai/route-preview-map.js +1 -0
  27. package/app/components/fleet/hierarchy-tree.js +1 -0
  28. package/app/services/route-engine.js +1 -0
  29. package/composer.json +1 -1
  30. package/extension.json +1 -1
  31. package/package.json +6 -4
  32. package/scripts/normalize-rgba-alpha.js +90 -0
  33. package/server/src/Http/Resources/v1/Fleet.php +8 -2
  34. package/server/src/Models/Driver.php +6 -0
  35. package/server/src/Models/Order.php +18 -6
  36. package/server/src/Models/Place.php +11 -3
  37. package/server/src/Providers/FleetOpsServiceProvider.php +26 -0
  38. package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +88 -0
  39. package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +108 -0
  40. package/server/src/Support/Ai/Capabilities/ConsoleNavigationCapability.php +55 -0
  41. package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +508 -0
  42. package/server/src/Support/Ai/Capabilities/DocsHelpCapability.php +49 -0
  43. package/server/src/Support/Ai/Capabilities/ImportOrdersPreviewCapability.php +66 -0
  44. package/server/src/Support/Ai/Capabilities/OperationalQueryCapability.php +319 -0
  45. package/server/src/Support/Ai/Capabilities/OptimizeOrderRouteCapability.php +240 -0
  46. package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +95 -0
  47. package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +195 -0
  48. package/server/src/Support/Ai/FleetOpsAiQueryResources.php +150 -0
  49. package/server/src/Support/Utils.php +52 -0
  50. package/server/tests/AiCreateOrderPreviewCapabilityTest.php +131 -0
  51. package/server/tests/AiOperationalQueryCapabilityTest.php +75 -0
  52. package/server/tests/FleetHierarchyResourceTest.php +12 -0
  53. package/server/tests/OrderPayloadPlaceResolutionTest.php +53 -0
  54. package/server/tests/PointResolutionTest.php +63 -0
  55. package/addon/components/table/cell/dropdown/action-item.hbs +0 -31
  56. package/addon/components/table/cell/dropdown/action-item.js +0 -62
  57. package/addon/components/table/cell/dropdown.hbs +0 -28
  58. package/addon/components/table/cell/dropdown.js +0 -91
  59. package/app/components/table/cell/dropdown/action-item.js +0 -1
  60. package/app/components/table/cell/dropdown.js +0 -1
@@ -0,0 +1,53 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Models\Order;
4
+
5
+ function fleetopsOrderPayloadResolutionProbe(bool $hasExistingUuid)
6
+ {
7
+ return new class($hasExistingUuid) extends Order {
8
+ public function __construct(private bool $hasExistingUuid = false)
9
+ {
10
+ parent::__construct();
11
+ }
12
+
13
+ public function shouldResolveForTest(array $attributes, string $role): bool
14
+ {
15
+ return $this->shouldResolvePayloadPlace($attributes, $role);
16
+ }
17
+
18
+ protected function hasExistingPayloadPlaceUuid(?array $attributes, string $role): bool
19
+ {
20
+ return $this->hasExistingUuid;
21
+ }
22
+ };
23
+ }
24
+
25
+ test('order payload creation prefers existing place uuid over embedded place object', function () {
26
+ $order = fleetopsOrderPayloadResolutionProbe(true);
27
+
28
+ expect($order->shouldResolveForTest([
29
+ 'pickup_uuid' => '628892cc-271c-89fd-cdaa-39d02b40bd13',
30
+ 'pickup' => [
31
+ 'uuid' => '628892cc-271c-89fd-cdaa-39d02b40bd13',
32
+ 'location' => [
33
+ 'bbox' => [103.851, 1.2816, 103.851, 1.2816],
34
+ 'type' => 'Point',
35
+ 'coordinates' => [103.851, 1.2816],
36
+ ],
37
+ ],
38
+ ], 'pickup'))->toBeFalse();
39
+ });
40
+
41
+ test('order payload creation resolves embedded place when no valid uuid exists', function () {
42
+ $order = fleetopsOrderPayloadResolutionProbe(false);
43
+
44
+ expect($order->shouldResolveForTest([
45
+ 'pickup' => [
46
+ 'location' => [
47
+ 'bbox' => [103.851, 1.2816, 103.851, 1.2816],
48
+ 'type' => 'Point',
49
+ 'coordinates' => [103.851, 1.2816],
50
+ ],
51
+ ],
52
+ ], 'pickup'))->toBeTrue();
53
+ });
@@ -0,0 +1,63 @@
1
+ <?php
2
+
3
+ use Fleetbase\FleetOps\Models\Place;
4
+ use Fleetbase\FleetOps\Support\Utils;
5
+
6
+ function fleetopsBboxPoint(): array
7
+ {
8
+ return [
9
+ 'bbox' => [103.851, 1.2816, 103.851, 1.2816],
10
+ 'type' => 'Point',
11
+ 'coordinates' => [103.851, 1.2816],
12
+ ];
13
+ }
14
+
15
+ test('point resolution accepts GeoJSON point with bbox', function () {
16
+ $point = Utils::getPointFromMixed(fleetopsBboxPoint());
17
+
18
+ expect($point->getLat())->toBe(1.2816)
19
+ ->and($point->getLng())->toBe(103.851);
20
+ });
21
+
22
+ test('point resolution accepts feature wrapped GeoJSON point with bbox', function () {
23
+ $point = Utils::getPointFromMixed([
24
+ 'type' => 'Feature',
25
+ 'geometry' => fleetopsBboxPoint(),
26
+ ]);
27
+
28
+ expect($point->getLat())->toBe(1.2816)
29
+ ->and($point->getLng())->toBe(103.851);
30
+ });
31
+
32
+ test('point resolution rejects malformed feature points cleanly', function () {
33
+ expect(fn () => Utils::getPointFromMixed([
34
+ 'type' => 'Feature',
35
+ 'geometry' => [
36
+ 'type' => 'Point',
37
+ 'coordinates' => [],
38
+ ],
39
+ ]))->toThrow(Exception::class, 'Attempted to resolve Point from invalid location.');
40
+ });
41
+
42
+ test('coordinate helper does not collapse bbox GeoJSON point to zero', function () {
43
+ $point = Utils::getPointFromCoordinates([
44
+ 'type' => 'Feature',
45
+ 'geometry' => fleetopsBboxPoint(),
46
+ ]);
47
+
48
+ expect($point->getLat())->toBe(1.2816)
49
+ ->and($point->getLng())->toBe(103.851);
50
+ });
51
+
52
+ test('structured place normalization accepts bbox GeoJSON point locations', function () {
53
+ $place = Place::mergeStructuredPlaceAttributes([
54
+ 'name' => 'AI Pickup',
55
+ 'street1' => '16 Simon Walk',
56
+ 'city' => 'Singapore',
57
+ 'country' => 'SG',
58
+ 'location' => fleetopsBboxPoint(),
59
+ ]);
60
+
61
+ expect($place['location']->getLat())->toBe(1.2816)
62
+ ->and($place['location']->getLng())->toBe(103.851);
63
+ });
@@ -1,31 +0,0 @@
1
- {{#if this.visible}}
2
- {{#if @columnAction.separator}}
3
- <div class="next-dd-menu-seperator"></div>
4
- {{else}}
5
- <div role="group" class="px-1">
6
- <a
7
- href="javascript:;"
8
- role="menuitem"
9
- class="next-dd-item {{@columnAction.class}} {{if this.disabled 'disabled'}}"
10
- disabled={{this.disabled}}
11
- {{on "click" (fn this.onClick @columnAction @row @dd)}}
12
- >
13
- {{#if @columnAction.icon}}
14
- <div class="w-6">
15
- <FaIcon class={{@columnAction.iconClass}} @icon={{@columnAction.icon}} @prefix={{@columnAction.iconPrefix}} />
16
- </div>
17
- {{/if}}
18
- {{@columnAction.label}}
19
- {{#if this.doesntHavePermissions}}
20
- <Attach::Tooltip @class="clean" @animation="scale" @placement={{or @columnAction.tooltipPlacement "right"}}>
21
- <InputInfo @text={{t "common.unauthorized"}} />
22
- </Attach::Tooltip>
23
- {{else if @columnAction.helpText}}
24
- <Attach::Tooltip @class="clean" @animation="scale" @placement={{or @columnAction.tooltipPlacement "right"}}>
25
- <InputInfo @text={{@columnAction.helpText}} @exampleText={{@columnAction.exampleText}} />
26
- </Attach::Tooltip>
27
- {{/if}}
28
- </a>
29
- </div>
30
- {{/if}}
31
- {{/if}}
@@ -1,62 +0,0 @@
1
- import Component from '@glimmer/component';
2
- import { tracked } from '@glimmer/tracking';
3
- import { inject as service } from '@ember/service';
4
- import { action } from '@ember/object';
5
- import { isNone } from '@ember/utils';
6
-
7
- export default class TableCellDropdownActionItemComponent extends Component {
8
- @service abilities;
9
- @tracked permissionRequired;
10
- @tracked doesntHavePermissions = false;
11
- @tracked disabled = false;
12
- @tracked visible = true;
13
-
14
- constructor(owner, { columnAction = {}, row = {}, disabled = false, permission = null }) {
15
- super(...arguments);
16
- this.permissionRequired = columnAction.permission ?? permission;
17
- this.disabled = this.disabledCheck(columnAction, this.permissionRequired, disabled);
18
- this.visible = (columnAction.visible ?? true) && this.visibilityCheck(columnAction, row);
19
- }
20
-
21
- @action onClick(columnAction, row, dd) {
22
- if (this.disabled) {
23
- return;
24
- }
25
-
26
- if (typeof dd?.actions?.close === 'function') {
27
- dd.actions.close();
28
- }
29
-
30
- if (typeof columnAction?.fn === 'function') {
31
- columnAction.fn(row);
32
- }
33
- }
34
-
35
- disabledCheck(columnAction, permission, defaultValue = false) {
36
- let disabled = columnAction.disabled ?? defaultValue;
37
- if (!disabled) {
38
- disabled = permission && this.abilities.cannot(permission);
39
- this.doesntHavePermissions = disabled;
40
- }
41
-
42
- return disabled;
43
- }
44
-
45
- visibilityCheck(columnAction, context) {
46
- const isVisible = columnAction.isVisible;
47
-
48
- if (isNone(context) || !isVisible) {
49
- return true;
50
- }
51
-
52
- if (typeof isVisible === 'boolean') {
53
- return isVisible;
54
- }
55
-
56
- if (typeof isVisible === 'function') {
57
- return isVisible(context);
58
- }
59
-
60
- return true;
61
- }
62
- }
@@ -1,28 +0,0 @@
1
- <div class="cell-dropdown-button overflow-visible {{@column.wrapperClass}}" {{did-insert this.setupComponent}} ...attributes>
2
- <DropdownButton
3
- @icon={{@column.ddButtonIcon}}
4
- @iconPrefix={{@column.ddButtonIconPrefix}}
5
- @text={{this.buttonText}}
6
- @size="xs"
7
- @horizontalPosition="left"
8
- @calculatePosition={{this.calculatePosition}}
9
- @renderInPlace={{true}}
10
- @onOpen={{this.onOpen}}
11
- @onClose={{this.onClose}}
12
- as |dd|
13
- >
14
- <div class="next-dd-menu next-dd-menu-table-dd mt-0i {{@column.ddMenuWrapperClass}}" role="menu" aria-orientation="vertical" aria-labelledby="user-menu">
15
- {{#if @column.ddMenuLabel}}
16
- <div class="next-dd-menu-label-container px-1">
17
- <div class="next-dd-menu-label {{@column.ddMenuLabelClass}}">
18
- {{@column.ddMenuLabel}}
19
- </div>
20
- </div>
21
- <div class="next-dd-menu-seperator"></div>
22
- {{/if}}
23
- {{#each @column.actions as |columnAction|}}
24
- <Table::Cell::Dropdown::ActionItem @columnAction={{columnAction}} @row={{@row}} @column={{@column}} @dd={{dd}} />
25
- {{/each}}
26
- </div>
27
- </DropdownButton>
28
- </div>
@@ -1,91 +0,0 @@
1
- import Component from '@glimmer/component';
2
- import { tracked } from '@glimmer/tracking';
3
- import { action, computed } from '@ember/object';
4
-
5
- export default class TableCellDropdownComponent extends Component {
6
- @tracked tableCellNode;
7
- defaultButtonText = 'Actions';
8
-
9
- @computed('args.column.ddButtonText', 'defaultButtonText') get buttonText() {
10
- const { ddButtonText } = this.args.column;
11
-
12
- if (ddButtonText === undefined) {
13
- return this.defaultButtonText;
14
- }
15
-
16
- if (ddButtonText === false) {
17
- return null;
18
- }
19
-
20
- return ddButtonText;
21
- }
22
-
23
- @action setupComponent(dropdownWrapperNode) {
24
- const tableCellNode = this.getOwnerTableCell(dropdownWrapperNode);
25
-
26
- if (tableCellNode) {
27
- tableCellNode.style.overflow = 'visible';
28
- }
29
-
30
- this.tableCellNode = tableCellNode;
31
- }
32
-
33
- @action onOpen() {
34
- if (!this.tableCellNode) {
35
- return;
36
- }
37
-
38
- const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 0;
39
- this.tableCellNode.style.zIndex = currentZIndex + 1;
40
- }
41
-
42
- @action onClose() {
43
- if (!this.tableCellNode) {
44
- return;
45
- }
46
-
47
- const currentZIndex = Number.parseInt(this.tableCellNode.style.zIndex, 10) || 1;
48
- this.tableCellNode.style.zIndex = currentZIndex - 1;
49
- }
50
-
51
- @action getOwnerTableCell(dropdownWrapperNode) {
52
- while (dropdownWrapperNode) {
53
- dropdownWrapperNode = dropdownWrapperNode.parentNode;
54
-
55
- if (dropdownWrapperNode?.tagName?.toLowerCase() === 'td') {
56
- return dropdownWrapperNode;
57
- }
58
- }
59
-
60
- return undefined;
61
- }
62
-
63
- @action onDropdownItemClick(columnAction, row, dd) {
64
- if (typeof dd?.actions?.close === 'function') {
65
- dd.actions.close();
66
- }
67
-
68
- if (typeof columnAction?.fn === 'function') {
69
- columnAction.fn(row);
70
- }
71
- }
72
-
73
- @action calculatePosition(trigger, content) {
74
- if (typeof this.args.column?.calculatePosition === 'function') {
75
- return this.args.column.calculatePosition(trigger, content);
76
- }
77
-
78
- const triggerRect = trigger.getBoundingClientRect();
79
- const contentRect = content?.getBoundingClientRect?.();
80
- const contentWidth = contentRect?.width || 224;
81
-
82
- const style = {
83
- position: 'fixed',
84
- marginTop: '0px',
85
- left: `${triggerRect.left - contentWidth - 3}px`,
86
- top: `${triggerRect.top}px`,
87
- };
88
-
89
- return { style };
90
- }
91
- }
@@ -1 +0,0 @@
1
- export { default } from '@fleetbase/fleetops-engine/components/table/cell/dropdown/action-item';
@@ -1 +0,0 @@
1
- export { default } from '@fleetbase/fleetops-engine/components/table/cell/dropdown';