@fleetbase/ember-ui 0.3.5 → 0.3.7

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.
@@ -4,7 +4,7 @@
4
4
  {{#let (resolve-component widget.component) as |ResolvedComponent|}}
5
5
  {{#if ResolvedComponent}}
6
6
  <GridStackItem id={{widget.id}} @options={{spread-widget-options (hash id=widget.id options=widget.grid_options)}} class="relative">
7
- {{component ResolvedComponent options=widget.options}}
7
+ {{component ResolvedComponent widget=widget options=widget.options}}
8
8
 
9
9
  {{#if @isEdit}}
10
10
  <div class="absolute top-2 right-2">
@@ -23,12 +23,12 @@ export default class DashboardComponent extends Component {
23
23
  * Creates an instance of DashboardComponent.
24
24
  * @memberof DashboardComponent
25
25
  */
26
- constructor(owner, { defaultDashboardId = 'dashboard', defaultDashboardName = 'Default Dashboard', showPanelWhenZeroWidgets = false }) {
26
+ constructor(owner, { defaultDashboardId = 'dashboard', defaultDashboardName = 'Default Dashboard', showPanelWhenZeroWidgets = false, extension = 'core' }) {
27
27
  super(...arguments);
28
28
  this.dashboard.reset(); // ensure service is reset when re-rendering
29
29
  next(() => {
30
30
  this.dashboard.showPanelWhenZeroWidgets = showPanelWhenZeroWidgets;
31
- this.dashboard.loadDashboards.perform(defaultDashboardId, defaultDashboardName);
31
+ this.dashboard.loadDashboards.perform({ defaultDashboardId, defaultDashboardName, extension });
32
32
  });
33
33
  }
34
34
 
@@ -63,7 +63,7 @@ export default class DashboardComponent extends Component {
63
63
  // Get the name from the modal options
64
64
  const { name } = modal.getOptions();
65
65
 
66
- await this.dashboard.createDashboard.perform(name);
66
+ await this.dashboard.createDashboard.perform(name, { extension: this.args.extension });
67
67
  done();
68
68
  },
69
69
  ...options,
@@ -1,6 +1,6 @@
1
1
  <div class="filter-string" ...attributes>
2
- <Input @value={{@value}} placeholder={{@placeholder}} {{on "input" this.onChange}} aria-label="Filter Input" autocomplete="false" class="form-input form-input-sm flex-1" />
3
- <button type="button" class="clear-button" disabled={{not @value}} alt="Clear" {{on "click" this.clear}}>
2
+ <Input @value={{this.value}} placeholder={{@placeholder}} {{on "input" this.onChange}} aria-label="Filter Input" autocomplete="false" class="form-input form-input-sm flex-1" />
3
+ <button type="button" class="clear-button" disabled={{not this.value}} alt="Clear" {{on "click" this.clear}}>
4
4
  <FaIcon @icon="times" />
5
5
  </button>
6
6
  </div>
@@ -1,10 +1,20 @@
1
1
  import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
2
3
  import { action } from '@ember/object';
3
4
 
4
5
  export default class FilterStringComponent extends Component {
6
+ @tracked value = '';
7
+
8
+ constructor(owner, { value = '' }) {
9
+ super(...arguments);
10
+ this.value = value;
11
+ }
12
+
5
13
  @action onChange({ target: { value } }) {
6
14
  const { onChange, filter } = this.args;
7
15
 
16
+ this.value = value;
17
+
8
18
  if (typeof onChange === 'function') {
9
19
  onChange(filter, value);
10
20
  }
@@ -13,6 +23,8 @@ export default class FilterStringComponent extends Component {
13
23
  @action clear() {
14
24
  const { onClear, filter } = this.args;
15
25
 
26
+ this.value = '';
27
+
16
28
  if (typeof onClear === 'function') {
17
29
  onClear(filter);
18
30
  }
@@ -5,25 +5,21 @@ import { tracked } from '@glimmer/tracking';
5
5
 
6
6
  export default class LayoutHeaderSidebarToggleComponent extends Component {
7
7
  @service universe;
8
+ @service sidebar;
8
9
  @tracked isSidebarVisible = true;
9
10
 
10
11
  @action toggleSidebar() {
11
- if (this.args.disabled === true) {
12
- return;
13
- }
14
-
15
- const sidebar = this.universe.sidebarContext;
12
+ if (this.args.disabled === true) return;
16
13
 
17
14
  if (this.isSidebarVisible) {
18
- sidebar.hideNow();
15
+ this.sidebar.hideNow();
19
16
  } else {
20
- sidebar.show();
17
+ this.sidebar.show();
21
18
  }
22
19
 
23
20
  this.isSidebarVisible = !this.isSidebarVisible;
24
-
25
21
  if (typeof this.args.onToggle === 'function') {
26
- this.args.onToggle(sidebar, this.isSidebarVisible);
22
+ this.args.onToggle(this.sidebar, this.isSidebarVisible);
27
23
  }
28
24
  }
29
25
  }
@@ -310,7 +310,7 @@ export default class ModelSelectComponent extends Component {
310
310
  }
311
311
 
312
312
  if (typeof onChangeId === 'function') {
313
- onChangeId(model.id, select);
313
+ onChangeId(model?.id ?? null, select);
314
314
  }
315
315
  }
316
316
  }
@@ -1,8 +1,11 @@
1
1
  <div class="query-builder-panel" ...attributes>
2
2
  <div class="query-builder-panel-header">
3
- <div class="query-builder-panel-title">
4
- <FaIcon @icon="columns" @size="sm" class="mr-2" />
5
- Select fields
3
+ <div class="flex flex-row items-center space-x-2">
4
+ <div class="query-builder-panel-title">
5
+ <FaIcon @icon="columns" @size="sm" class="mr-2" />
6
+ Select fields
7
+ </div>
8
+ <Input @value={{this.searchQuery}} class="form-input form-input-sm" placeholder="Search for a column" />
6
9
  </div>
7
10
  <div class="text-xs text-gray-500">
8
11
  {{if this.selectedColumns.length (concat this.selectedColumns.length " selected") "None selected"}}
@@ -11,7 +14,7 @@
11
14
 
12
15
  <div class="query-builder-panel-content no-padding">
13
16
  <div class="{{if @columns 'max-h-80 overflow-y-auto' ''}} grid grid-cols-1 gap-2 lg:grid-cols-2 px-2 py-3">
14
- {{#each @columns as |column|}}
17
+ {{#each this.filteredColumns as |column|}}
15
18
  <div class="column-item {{if (includes column.name (map-by 'name' this.selectedColumns)) 'selected'}}">
16
19
  <div class="column-checkbox-wrapper">
17
20
  <Checkbox @checked={{includes column.name (map-by "name" this.selectedColumns)}} @onToggle={{fn this.selectColumn column}} />
@@ -5,6 +5,22 @@ import { action } from '@ember/object';
5
5
  export default class QueryBuilderColumnSelectComponent extends Component {
6
6
  @tracked selectedColumns = [];
7
7
  @tracked columnAliases = {};
8
+ @tracked searchQuery = '';
9
+
10
+ get filteredColumns() {
11
+ const columns = this.args.columns ?? [];
12
+ const query = (this.searchQuery ?? '').trim().toLowerCase();
13
+
14
+ if (!query) {
15
+ return columns;
16
+ }
17
+
18
+ return columns.filter((col) => {
19
+ const name = String(col.name ?? '').toLowerCase();
20
+ const label = String(col.label ?? '').toLowerCase();
21
+ return name.includes(query) || label.includes(query);
22
+ });
23
+ }
8
24
 
9
25
  constructor() {
10
26
  super(...arguments);
@@ -12,8 +28,7 @@ export default class QueryBuilderColumnSelectComponent extends Component {
12
28
  this.columnAliases = this.args.columnAliases || {};
13
29
  }
14
30
 
15
- @action
16
- selectColumn(column) {
31
+ @action selectColumn(column) {
17
32
  const isSelected = this.selectedColumns.includes(column);
18
33
 
19
34
  if (isSelected) {
@@ -29,8 +44,7 @@ export default class QueryBuilderColumnSelectComponent extends Component {
29
44
  this.notifyChange();
30
45
  }
31
46
 
32
- @action
33
- updateAlias(columnName, event) {
47
+ @action updateAlias(columnName, event) {
34
48
  const aliasValue = event.target.value.trim();
35
49
 
36
50
  if (aliasValue) {
@@ -48,16 +62,14 @@ export default class QueryBuilderColumnSelectComponent extends Component {
48
62
  this.notifyChange();
49
63
  }
50
64
 
51
- @action
52
- selectAllColumns() {
65
+ @action selectAllColumns() {
53
66
  if (this.args.columns) {
54
67
  this.selectedColumns = [...this.args.columns];
55
68
  this.notifyChange();
56
69
  }
57
70
  }
58
71
 
59
- @action
60
- clearAllColumns() {
72
+ @action clearAllColumns() {
61
73
  this.selectedColumns = [];
62
74
  this.columnAliases = {};
63
75
  this.notifyChange();
@@ -379,8 +379,7 @@ export default class QueryBuilderConditionsComponent extends Component {
379
379
  });
380
380
  }
381
381
 
382
- @action
383
- updateGroupOperator(groupIndex, operator) {
382
+ @action updateGroupOperator(groupIndex, operator) {
384
383
  // same immutable pattern for the group object
385
384
  const groups = [...this.conditionGroups];
386
385
  groups[groupIndex] = { ...groups[groupIndex], operator };
@@ -388,8 +387,7 @@ export default class QueryBuilderConditionsComponent extends Component {
388
387
  this.notifyChange();
389
388
  }
390
389
 
391
- @action
392
- reorderConditionGroups({ sourceList, sourceIndex, targetList, targetIndex }) {
390
+ @action reorderConditionGroups({ sourceList, sourceIndex, targetList, targetIndex }) {
393
391
  // no change? bail
394
392
  if (sourceList === targetList && sourceIndex === targetIndex) return;
395
393
 
@@ -11,10 +11,8 @@
11
11
 
12
12
  <div class="query-builder-panel-content">
13
13
  {{#if this.canGroup}}
14
-
15
14
  <div class="space-y-4">
16
15
  <div class="grid grid-cols-1 md:grid-cols-3 gap-3">
17
-
18
16
  <div>
19
17
  <label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
20
18
  Group By Column
@@ -92,7 +90,7 @@
92
90
  @icon="save"
93
91
  @size="xs"
94
92
  @onClick={{this.addGroupBy}}
95
- @disabled={{not (and this.selectedGroupBy this.selectedAggregateFn this.selectedAggregateBy)}}
93
+ @disabled={{this.isAddGroupingDisabled}}
96
94
  />
97
95
  </div>
98
96
 
@@ -102,7 +100,7 @@
102
100
  Active Groupings
103
101
  </div>
104
102
 
105
- <DragSortList @items={{this.groupByItems}} @onChange={{this.reorderGroupBy}} @dragEndAction={{this.reorderGroupBy}} class="drag-sort-list" as |item index|>
103
+ <DragSortList @items={{this.groupByItems}} @dragEndAction={{this.reorderGroupBy}} class="drag-sort-list" as |item index|>
106
104
  <div class="group-sort-item">
107
105
  <div class="drag-handle">
108
106
  <FaIcon @icon="grip-vertical" @size="sm" />
@@ -50,22 +50,25 @@ export default class QueryBuilderGroupByComponent extends Component {
50
50
  get availableAggregateColumns() {
51
51
  if (!this.selectedAggregateFn) return [];
52
52
 
53
- // Use allSelectedColumns from parent if available
54
53
  const columnsToUse = this.args.allSelectedColumns || this.args.selectedColumns || [];
54
+ const fn = this.selectedAggregateFn.value;
55
55
 
56
- // For COUNT, we can use any selected column or *
57
- if (this.selectedAggregateFn.value === 'count') {
56
+ if (fn === 'count') {
58
57
  return [{ name: '*', label: 'All Records', type: 'count', full: '*' }, ...columnsToUse];
59
58
  }
60
59
 
61
- // For SUM, AVG, MIN, MAX - only numeric columns from selected columns
62
- if (['sum', 'avg', 'min', 'max'].includes(this.selectedAggregateFn.value)) {
63
- return columnsToUse.filter((column) => ['number', 'integer', 'decimal', 'float'].includes(column.type));
60
+ if (fn === 'sum' || fn === 'avg') {
61
+ // numeric only
62
+ return columnsToUse.filter((c) => ['integer', 'decimal', 'number', 'float'].includes(c.type));
64
63
  }
65
64
 
66
- // For GROUP_CONCAT - string columns from selected columns
67
- if (this.selectedAggregateFn.value === 'group_concat') {
68
- return columnsToUse.filter((column) => ['string', 'text'].includes(column.type));
65
+ if (fn === 'min' || fn === 'max') {
66
+ // numeric + datetime + date + string
67
+ return columnsToUse.filter((c) => ['integer', 'decimal', 'number', 'float', 'date', 'datetime', 'timestamp', 'string', 'text'].includes(c.type));
68
+ }
69
+
70
+ if (fn === 'group_concat') {
71
+ return columnsToUse.filter((c) => ['string', 'text'].includes(c.type));
69
72
  }
70
73
 
71
74
  return columnsToUse;
@@ -93,24 +96,49 @@ export default class QueryBuilderGroupByComponent extends Component {
93
96
  return null;
94
97
  }
95
98
 
96
- @action
97
- selectGroupBy(column) {
99
+ get isAddGroupingDisabled() {
100
+ const hasGroupBy = !!this.selectedGroupBy;
101
+ const fn = this.selectedAggregateFn?.value;
102
+ const hasFn = !!fn;
103
+ const hasBy = !!this.selectedAggregateBy;
104
+
105
+ if (!hasGroupBy || !hasFn) {
106
+ return true;
107
+ }
108
+
109
+ // COUNT requires a selection: either "*" or a column (you can auto-select "*" elsewhere)
110
+ if (fn === 'count') {
111
+ return !hasBy;
112
+ }
113
+
114
+ // For SUM/AVG/MIN/MAX/GROUP_CONCAT we need:
115
+ // - at least one compatible column available
116
+ // - a selected "aggregate by" column
117
+ const avail = this.availableAggregateColumns ?? [];
118
+ const hasCompatible = avail.length > 0;
119
+
120
+ return !(hasCompatible && hasBy);
121
+ }
122
+
123
+ @action selectGroupBy(column) {
98
124
  this.selectedGroupBy = column;
99
125
  }
100
126
 
101
- @action
102
- selectAggregateFn(fn) {
127
+ @action selectAggregateFn(fn) {
103
128
  this.selectedAggregateFn = fn;
104
- this.selectedAggregateBy = null; // Reset aggregate column when function changes
129
+ // Optional UX: auto-select "*" when choosing COUNT
130
+ if (fn?.value === 'count') {
131
+ this.selectedAggregateBy = { name: '*', label: 'All Records', type: 'count', full: '*' };
132
+ } else {
133
+ this.selectedAggregateBy = null;
134
+ }
105
135
  }
106
136
 
107
- @action
108
- selectAggregateBy(column) {
137
+ @action selectAggregateBy(column) {
109
138
  this.selectedAggregateBy = column;
110
139
  }
111
140
 
112
- @action
113
- addGroupBy() {
141
+ @action addGroupBy() {
114
142
  if (this.selectedGroupBy && this.selectedAggregateFn && this.selectedAggregateBy) {
115
143
  // Validate that the groupBy column is actually selected
116
144
  const isGroupByColumnSelected = this.args.selectedColumns?.some((col) => col.full === this.selectedGroupBy.full);
@@ -138,23 +166,35 @@ export default class QueryBuilderGroupByComponent extends Component {
138
166
  }
139
167
  }
140
168
 
141
- @action
142
- removeGroupBy(index) {
169
+ @action removeGroupBy(index) {
143
170
  this.groupByItems = this.groupByItems.filter((_, i) => i !== index);
144
171
  this.notifyChange();
145
172
  }
146
173
 
147
- @action
148
- reorderGroupBy(newOrder) {
149
- this.groupByItems = newOrder;
174
+ // @action reorderGroupBy(newOrder) {
175
+ // this.groupByItems = newOrder;
176
+ // this.notifyChange();
177
+ // }
178
+
179
+ @action reorderGroupBy({ sourceList, sourceIndex, targetList, targetIndex }) {
180
+ // no change? bail
181
+ if (sourceList === targetList && sourceIndex === targetIndex) return;
182
+
183
+ // mutate the EmberArray in-place (per README)
184
+ const item = sourceList.objectAt(sourceIndex);
185
+ sourceList.removeAt(sourceIndex);
186
+ targetList.insertAt(targetIndex, item);
187
+
188
+ // ensure Glimmer sees a change even if it misses EmberArray observers
189
+ this.groupByItems = [...this.groupByItems];
190
+
150
191
  this.notifyChange();
151
192
  }
152
193
 
153
194
  /**
154
195
  * Validate existing group by items when selected columns change
155
196
  */
156
- @action
157
- validateGroupByItems() {
197
+ @action validateGroupByItems() {
158
198
  if (!this.args.selectedColumns?.length) {
159
199
  // Clear all grouping if no columns selected
160
200
  if (this.groupByItems.length > 0) {
@@ -68,13 +68,6 @@
68
68
  >
69
69
  Select None
70
70
  </button>
71
- <button
72
- type="button"
73
- class="text-xs px-2 py-1 bg-green-100 dark:bg-green-800 text-green-700 dark:text-green-300 rounded hover:bg-green-200 dark:hover:bg-green-700"
74
- {{on "click" (fn this.selectCommonJoinColumns relationship.key)}}
75
- >
76
- Common Fields
77
- </button>
78
71
  </div>
79
72
 
80
73
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-48 overflow-y-auto">
@@ -19,7 +19,6 @@ export default class QueryBuilderJoinsComponent extends Component {
19
19
  key,
20
20
  table: key,
21
21
  ...relationship,
22
- columns: this.getColumnsForTable(key),
23
22
  };
24
23
  });
25
24
  }
@@ -28,64 +27,6 @@ export default class QueryBuilderJoinsComponent extends Component {
28
27
  return this.joins.reduce((total, join) => total + (join.selectedColumns?.length || 0), 0);
29
28
  }
30
29
 
31
- getColumnsForTable(tableName) {
32
- // Get columns from the schema registry or args
33
- if (this.args.getTableColumns) {
34
- return this.args.getTableColumns(tableName);
35
- }
36
-
37
- // Fallback to mock columns for development
38
- const mockColumns = {
39
- drivers: [
40
- { name: 'uuid', label: 'ID', type: 'string' },
41
- { name: 'name', label: 'Driver Name', type: 'string' },
42
- { name: 'email', label: 'Email', type: 'string' },
43
- { name: 'phone', label: 'Phone', type: 'string' },
44
- { name: 'status', label: 'Status', type: 'string' },
45
- { name: 'online', label: 'Online Status', type: 'boolean' },
46
- { name: 'created_at', label: 'Created At', type: 'datetime' },
47
- { name: 'updated_at', label: 'Updated At', type: 'datetime' },
48
- ],
49
- vehicles: [
50
- { name: 'uuid', label: 'ID', type: 'string' },
51
- { name: 'make', label: 'Make', type: 'string' },
52
- { name: 'model', label: 'Model', type: 'string' },
53
- { name: 'year', label: 'Year', type: 'number' },
54
- { name: 'vin', label: 'VIN', type: 'string' },
55
- { name: 'plate_number', label: 'Plate Number', type: 'string' },
56
- { name: 'status', label: 'Status', type: 'string' },
57
- { name: 'created_at', label: 'Created At', type: 'datetime' },
58
- ],
59
- contacts: [
60
- { name: 'uuid', label: 'ID', type: 'string' },
61
- { name: 'name', label: 'Contact Name', type: 'string' },
62
- { name: 'email', label: 'Email', type: 'string' },
63
- { name: 'phone', label: 'Phone', type: 'string' },
64
- { name: 'type', label: 'Contact Type', type: 'string' },
65
- { name: 'created_at', label: 'Created At', type: 'datetime' },
66
- ],
67
- places: [
68
- { name: 'uuid', label: 'ID', type: 'string' },
69
- { name: 'name', label: 'Place Name', type: 'string' },
70
- { name: 'street1', label: 'Street Address', type: 'string' },
71
- { name: 'city', label: 'City', type: 'string' },
72
- { name: 'province', label: 'Province/State', type: 'string' },
73
- { name: 'country', label: 'Country', type: 'string' },
74
- { name: 'postal_code', label: 'Postal Code', type: 'string' },
75
- ],
76
- };
77
-
78
- return mockColumns[tableName] || [];
79
- }
80
-
81
- getCommonColumns(tableName) {
82
- // Return commonly used columns for quick selection
83
- const commonColumnNames = ['name', 'status', 'email', 'phone', 'created_at'];
84
- const allColumns = this.getColumnsForTable(tableName);
85
-
86
- return allColumns.filter((column) => commonColumnNames.includes(column.name) || column.name.includes('name') || column.name.includes('status'));
87
- }
88
-
89
30
  @action
90
31
  isJoined(relationshipKey) {
91
32
  return this.joins.some((join) => join.key === relationshipKey);
@@ -186,27 +127,6 @@ export default class QueryBuilderJoinsComponent extends Component {
186
127
  this.notifyChange();
187
128
  }
188
129
 
189
- @action
190
- selectCommonJoinColumns(relationshipKey) {
191
- const joinIndex = this.joins.findIndex((join) => join.key === relationshipKey);
192
- if (joinIndex === -1) return;
193
-
194
- const updatedJoins = [...this.joins];
195
- const join = { ...updatedJoins[joinIndex] };
196
- const commonColumns = this.getCommonColumns(relationshipKey);
197
-
198
- join.selectedColumns = commonColumns.map((column) => ({
199
- ...column,
200
- table: relationshipKey,
201
- full: `${relationshipKey}.${column.name}`,
202
- label: `${join.label} - ${column.label || column.name}`,
203
- }));
204
-
205
- updatedJoins[joinIndex] = join;
206
- this.joins = updatedJoins;
207
- this.notifyChange();
208
- }
209
-
210
130
  @action
211
131
  updateJoinColumnAlias(relationshipKey, columnName, event) {
212
132
  const joinIndex = this.joins.findIndex((join) => join.key === relationshipKey);
@@ -1,13 +1,17 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { tracked } from '@glimmer/tracking';
3
3
  import { action } from '@ember/object';
4
+ import { next } from '@ember/runloop';
4
5
 
5
6
  export default class QueryBuilderLimitComponent extends Component {
6
7
  @tracked limit = null;
7
8
 
8
9
  constructor() {
9
10
  super(...arguments);
10
- this.limit = this.args.limit || null;
11
+ this.limit = this.args.limit || 50;
12
+
13
+ // set detault limit
14
+ next(() => this.setLimit(this.limit));
11
15
  }
12
16
 
13
17
  get quickLimits() {
@@ -23,21 +27,18 @@ export default class QueryBuilderLimitComponent extends Component {
23
27
  ];
24
28
  }
25
29
 
26
- @action
27
- setLimit(value) {
30
+ @action setLimit(value) {
28
31
  const numValue = parseInt(value, 10);
29
32
  this.limit = isNaN(numValue) || numValue <= 0 ? null : numValue;
30
33
  this.notifyChange();
31
34
  }
32
35
 
33
- @action
34
- setQuickLimit(value) {
36
+ @action setQuickLimit(value) {
35
37
  this.limit = value;
36
38
  this.notifyChange();
37
39
  }
38
40
 
39
- @action
40
- clearLimit() {
41
+ @action clearLimit() {
41
42
  this.limit = null;
42
43
  this.notifyChange();
43
44
  }
@@ -68,7 +68,7 @@
68
68
  Sort Order (drag to reorder)
69
69
  </div>
70
70
 
71
- <DragSortList @items={{this.sortByItems}} @onChange={{this.reorderSortBy}} @dragEndAction={{this.reorderSortBy}} class="drag-sort-list" as |item index|>
71
+ <DragSortList @items={{this.sortByItems}} @dragEndAction={{this.reorderSortBy}} class="drag-sort-list" as |item index|>
72
72
  <div class="group-sort-item">
73
73
  <div class="drag-handle">
74
74
  <FaIcon @icon="grip-vertical" @size="sm" />
@@ -62,18 +62,15 @@ export default class QueryBuilderSortByComponent extends Component {
62
62
  return null;
63
63
  }
64
64
 
65
- @action
66
- selectSortColumn(column) {
65
+ @action selectSortColumn(column) {
67
66
  this.selectedSortBy = column;
68
67
  }
69
68
 
70
- @action
71
- selectSortDirection(direction) {
69
+ @action selectSortDirection(direction) {
72
70
  this.selectedSortDirection = direction;
73
71
  }
74
72
 
75
- @action
76
- addSortBy() {
73
+ @action addSortBy() {
77
74
  if (this.selectedSortBy && this.selectedSortDirection) {
78
75
  // Validate that the sort column is actually selected
79
76
  const isSortColumnSelected = this.args.selectedColumns?.some((col) => col.full === this.selectedSortBy.full);
@@ -113,20 +110,32 @@ export default class QueryBuilderSortByComponent extends Component {
113
110
  }
114
111
  }
115
112
 
116
- @action
117
- removeSortBy(index) {
113
+ @action removeSortBy(index) {
118
114
  this.sortByItems = this.sortByItems.filter((_, i) => i !== index);
119
115
  this.notifyChange();
120
116
  }
121
117
 
122
- @action
123
- reorderSortBy(newOrder) {
124
- this.sortByItems = newOrder;
118
+ // @action reorderSortBy(newOrder) {
119
+ // this.sortByItems = newOrder;
120
+ // this.notifyChange();
121
+ // }
122
+
123
+ @action reorderGroupBy({ sourceList, sourceIndex, targetList, targetIndex }) {
124
+ // no change? bail
125
+ if (sourceList === targetList && sourceIndex === targetIndex) return;
126
+
127
+ // mutate the EmberArray in-place (per README)
128
+ const item = sourceList.objectAt(sourceIndex);
129
+ sourceList.removeAt(sourceIndex);
130
+ targetList.insertAt(targetIndex, item);
131
+
132
+ // ensure Glimmer sees a change even if it misses EmberArray observers
133
+ this.sortByItems = [...this.sortByItems];
134
+
125
135
  this.notifyChange();
126
136
  }
127
137
 
128
- @action
129
- toggleSortDirection(index) {
138
+ @action toggleSortDirection(index) {
130
139
  const updatedItems = [...this.sortByItems];
131
140
  const currentDirection = updatedItems[index].direction.value;
132
141
  const newDirection = currentDirection === 'asc' ? this.directions[1] : this.directions[0];
@@ -143,8 +152,7 @@ export default class QueryBuilderSortByComponent extends Component {
143
152
  /**
144
153
  * Validate existing sort items when selected columns change
145
154
  */
146
- @action
147
- validateSortItems() {
155
+ @action validateSortItems() {
148
156
  const columnsToUse = this.args.allSelectedColumns || this.args.selectedColumns || [];
149
157
 
150
158
  if (!columnsToUse.length) {
@@ -1,6 +1,6 @@
1
1
  <div class="details-wrapper" ...attributes>
2
2
  <ContentPanel @title="Report Details" @open={{true}} @wrapperClass="bordered-top">
3
- <div class="grid grid-cols-2 gap-2 text-xs dark:text-gray-100 px-1.5">
3
+ <div class="grid grid-cols-2 gap-2 text-xs dark:text-gray-100">
4
4
  <div class="field-info-container">
5
5
  <div class="field-name">Title</div>
6
6
  <div class="field-value">{{n-a @resource.title}}</div>
@@ -9,12 +9,6 @@
9
9
  <div class="field-name">Description</div>
10
10
  <div class="field-value">{{n-a @resource.description}}</div>
11
11
  </div>
12
- <div class="field-info-container">
13
- <div class="field-name">Status</div>
14
- <div class="field-value">
15
- <Badge @status={{@resource.status}} />
16
- </div>
17
- </div>
18
12
  <div class="field-info-container">
19
13
  <div class="field-name">Date Created</div>
20
14
  <div class="field-value">{{@resource.createdAt}}</div>
@@ -24,6 +18,8 @@
24
18
  <div class="field-value flex flex-row items-center space-x-1">
25
19
  {{#each @resource.tags as |tag|}}
26
20
  <div class="rounded-xl border text-sm border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-0.5 flex items-center justify-center">{{tag}}</div>
21
+ {{else}}
22
+ <div>-</div>
27
23
  {{/each}}
28
24
  </div>
29
25
  </div>
@@ -218,7 +218,7 @@ export default class UnitInputComponent extends Component {
218
218
  * @memberof UnitInputComponent
219
219
  */
220
220
  @computed('selectedUnitObject.name', 'unit') get unitName() {
221
- return this.selectedUnitObject.name ?? this.unit;
221
+ return this.selectedUnitObject?.name ?? this.unit;
222
222
  }
223
223
 
224
224
  /**
@@ -53,10 +53,10 @@ export default class DashboardService extends Service {
53
53
  /**
54
54
  * Task for loading dashboards from the store. It sets the current dashboard and checks if adding widget is necessary.
55
55
  */
56
- @task *loadDashboards(defaultDashboardId = 'dashboard', defaultDashboardName = 'Default Dashboard') {
56
+ @task *loadDashboards({ defaultDashboardId = 'dashboard', defaultDashboardName = 'Default Dashboard', extension = 'core' }) {
57
57
  this.universe.registerDashboard(defaultDashboardId);
58
58
 
59
- const dashboards = yield this.store.findAll('dashboard');
59
+ const dashboards = yield this.store.query('dashboard', { limit: -1, extension });
60
60
  if (isArray(dashboards)) {
61
61
  this.dashboards = typeof dashboards.toArray === 'function' ? dashboards.toArray() : dashboards;
62
62
 
@@ -98,16 +98,18 @@ export default class DashboardService extends Service {
98
98
  * Task for creating a new dashboard. It handles dashboard creation, success notification, and dashboard selection.
99
99
  * @param {string} name - Name of the new dashboard.
100
100
  */
101
- @task *createDashboard(name) {
102
- const dashboardRecord = this.store.createRecord('dashboard', { name, is_default: true });
103
- const dashboard = yield dashboardRecord.save().catch((error) => {
104
- this.notifications.serverError(error);
105
- });
106
-
107
- if (dashboard) {
108
- this.notifications.success(this.intl.t('services.dashboard-service.create-dashboard-success-notification', { dashboardName: dashboard.name }));
109
- this.selectDashboard.perform(dashboard);
110
- this.dashboards.pushObject(dashboard);
101
+ @task *createDashboard(name, attributes = {}) {
102
+ try {
103
+ const dashboardRecord = this.store.createRecord('dashboard', { name, is_default: true, ...attributes });
104
+ const dashboard = yield dashboardRecord.save();
105
+
106
+ if (dashboard) {
107
+ this.notifications.success(this.intl.t('services.dashboard-service.create-dashboard-success-notification', { dashboardName: dashboard.name }));
108
+ this.selectDashboard.perform(dashboard);
109
+ this.dashboards.pushObject(dashboard);
110
+ }
111
+ } catch (err) {
112
+ this.notifications.serverError(err);
111
113
  }
112
114
  }
113
115
 
@@ -79,12 +79,14 @@
79
79
 
80
80
  .status-badge.ready-status-badge > span,
81
81
  .status-badge.verified-status-badge > span,
82
+ .status-badge.allocated-status-badge > span,
82
83
  .status-badge.emerald-status-badge > span {
83
84
  @apply bg-emerald-800 border-emerald-700 text-emerald-100;
84
85
  }
85
86
 
86
87
  .status-badge.ready-status-badge > span svg,
87
88
  .status-badge.verified-status-badge > span svg,
89
+ .status-badge.allocated-status-badge > span svg,
88
90
  .status-badge.emerald-status-badge > span svg {
89
91
  @apply text-emerald-300;
90
92
  }
@@ -123,16 +125,21 @@
123
125
  @apply text-slate-300;
124
126
  }
125
127
 
128
+ .status-badge.disabled-status-badge > span,
126
129
  .status-badge.gray-status-badge > span {
127
130
  @apply bg-gray-800 border-gray-700 text-gray-100;
128
131
  }
129
132
 
133
+ .status-badge.disabled-status-badge > span svg,
130
134
  .status-badge.gray-status-badge > span svg {
131
135
  @apply text-gray-300;
132
136
  }
133
137
 
134
138
  .status-badge[class*='5'] > span,
135
139
  .status-badge.red-status-badge > span,
140
+ .status-badge.faulty-status-badge > span,
141
+ .status-badge.degraded-status-badge > span,
142
+ .status-badge.error-status-badge > span,
136
143
  .status-badge.escalated-status-badge > span,
137
144
  .status-badge.high-status-badge > span,
138
145
  .status-badge.rejected-status-badge > span,
@@ -147,6 +154,9 @@
147
154
 
148
155
  .status-badge[class*='5'] > span svg,
149
156
  .status-badge.red-status-badge > span svg,
157
+ .status-badge.faulty-status-badge > span svg,
158
+ .status-badge.degraded-status-badge > span svg,
159
+ .status-badge.error-status-badge > span svg,
150
160
  .status-badge.escalated-status-badge > span svg,
151
161
  .status-badge.high-status-badge > span svg,
152
162
  .status-badge.rejected-status-badge > span svg,
@@ -161,6 +171,7 @@
161
171
  }
162
172
 
163
173
  .status-badge[class*='4'] > span,
174
+ .status-badge.idle-status-badge > span,
164
175
  .status-badge.draft-status-badge > span,
165
176
  .status-badge.yellow-status-badge > span,
166
177
  .status-badge.re-opened-status-badge > span,
@@ -168,8 +179,10 @@
168
179
  .status-badge.driver-enroute-status-badge > span,
169
180
  .status-badge.offline-status-badge > span,
170
181
  .status-badge.pending-review-status-badge > span,
182
+ .status-badge.pending-activation-status-badge > span,
171
183
  .status-badge.awaiting-review-status-badge > span,
172
184
  .status-badge.scheduled-maintenance-status-badge > span,
185
+ .status-badge.maintenance-status-badge > span,
173
186
  .status-badge.test-status-badge > span,
174
187
  .status-badge.warning-status-badge > span,
175
188
  .status-badge.preparing-status-badge > span,
@@ -181,6 +194,7 @@
181
194
  }
182
195
 
183
196
  .status-badge[class*='4'] > span svg,
197
+ .status-badge.idle-status-badge > span svg,
184
198
  .status-badge.draft-status-badge > span svg,
185
199
  .status-badge.yellow-status-badge > span svg,
186
200
  .status-badge.re-opened-status-badge > span svg,
@@ -188,8 +202,10 @@
188
202
  .status-badge.driver-enroute-status-badge > span svg,
189
203
  .status-badge.offline-status-badge > span svg,
190
204
  .status-badge.pending-review-status-badge > span svg,
205
+ .status-badge.pending-activation-status-badge > span svg,
191
206
  .status-badge.awaiting-review-status-badge > span svg,
192
207
  .status-badge.scheduled-maintenance-status-badge > span svg,
208
+ .status-badge.maintenance-status-badge > span svg,
193
209
  .status-badge.test-status-badge > span svg,
194
210
  .status-badge.warning-status-badge > span svg,
195
211
  .status-badge.preparing-status-badge > span svg,
@@ -201,6 +217,7 @@
201
217
  }
202
218
 
203
219
  .status-badge.indigo-status-badge > span,
220
+ .status-badge.initialized-status-badge > span,
204
221
  .status-badge.operational-suggestion-status-badge > span,
205
222
  .status-badge.dispatched-status-badge > span,
206
223
  .status-badge.matched-status-badge > span,
@@ -209,6 +226,7 @@
209
226
  }
210
227
 
211
228
  .status-badge.indigo-status-badge > span svg,
229
+ .status-badge.initialized-status-badge > span svg,
212
230
  .status-badge.operational-suggestion-status-badge > span svg,
213
231
  .status-badge.dispatched-status-badge > span svg,
214
232
  .status-badge.matched-status-badge > span svg,
@@ -217,6 +235,9 @@
217
235
  }
218
236
 
219
237
  .status-badge.orange-status-badge > span,
238
+ .status-badge.decommissioned-status-badge > span,
239
+ .status-badge.disconnected-status-badge > span,
240
+ .status-badge.calibrating-status-badge > span,
220
241
  .status-badge.awaiting-parts-status-badge > span,
221
242
  .status-badge.monitoring-status-badge > span,
222
243
  .status-badge.triage-status-badge > span,
@@ -227,6 +248,9 @@
227
248
  }
228
249
 
229
250
  .status-badge.orange-status-badge > span svg,
251
+ .status-badge.decommissioned-status-badge > span svg,
252
+ .status-badge.disconnected-status-badge > span svg,
253
+ .status-badge.calibrating-status-badge > span svg,
230
254
  .status-badge.awaiting-parts-status-badge > span svg,
231
255
  .status-badge.monitoring-status-badge > span svg,
232
256
  .status-badge.triage-status-badge > span svg,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-ui",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.",
5
5
  "keywords": [
6
6
  "fleetbase-ui",