@fleetbase/ember-ui 0.3.7 → 0.3.8

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.
@@ -33,7 +33,8 @@ export default class CustomFieldsManagerComponent extends Component {
33
33
  super(...arguments);
34
34
  this.subjects = subjects ?? [];
35
35
  next(() => {
36
- if (!this.subjects) return;
36
+ if (!this.subjects || this.subjects.length === 0) return;
37
+ // Load the first subject immediately
37
38
  this.loadCustomFields.perform(this.subjects[0]);
38
39
  });
39
40
  }
@@ -51,7 +52,44 @@ export default class CustomFieldsManagerComponent extends Component {
51
52
  }
52
53
  }
53
54
 
55
+ /**
56
+ * Restore custom fields from cache for subjects that haven't been loaded yet
57
+ * This method checks the CustomFieldsRegistry service cache and restores
58
+ * previously loaded data to avoid losing it during navigation
59
+ */
60
+ async restoreFromCache() {
61
+ if (!this.subjects || this.subjects.length <= 1) return;
62
+
63
+ // Skip the first subject as it's loaded in constructor
64
+ const subjectsToRestore = this.subjects.slice(1);
65
+
66
+ for (const subject of subjectsToRestore) {
67
+ try {
68
+ const company = await this.currentUser.loadCompany();
69
+ const loadOptions = {
70
+ groupedFor: `${underscore(subject.model)}_custom_field_group`,
71
+ fieldFor: subject.type,
72
+ };
73
+
74
+ // Check if we have a cached manager for this subject
75
+ const cachedManager = this.customFieldsRegistry.forSubject(company, { loadOptions });
76
+
77
+ // Only restore if we have cached groups data
78
+ if (cachedManager && cachedManager.groups && cachedManager.groups.length > 0) {
79
+ this.#updateSubject(subject, (s) => {
80
+ return { ...s, groups: cachedManager.groups };
81
+ });
82
+ }
83
+ } catch (err) {
84
+ // Silently continue if cache restore fails for a subject
85
+ console.warn(`Failed to restore cache for subject ${subject.model}:`, err);
86
+ }
87
+ }
88
+ }
89
+
54
90
  @task *loadCustomFields(subject) {
91
+ if (!subject) return;
92
+
55
93
  try {
56
94
  const company = yield this.currentUser.loadCompany();
57
95
  const customFieldsManager = yield this.customFieldsRegistry.loadSubjectCustomFields.perform(company, {
@@ -60,12 +98,14 @@ export default class CustomFieldsManagerComponent extends Component {
60
98
  fieldFor: subject.type,
61
99
  },
62
100
  });
101
+
63
102
  this.#updateSubject(subject, (s) => {
64
103
  return { ...s, groups: customFieldsManager.customFieldGroups };
65
104
  });
66
105
 
67
106
  return customFieldsManager;
68
107
  } catch (err) {
108
+ console.error(`❌ Failed to load custom fields for ${subject.model}:`, err);
69
109
  this.notifications.serverError(err);
70
110
  }
71
111
  }
@@ -127,7 +167,7 @@ export default class CustomFieldsManagerComponent extends Component {
127
167
 
128
168
  try {
129
169
  await group.destroyRecord();
130
- await this.loadCustomFields.perform(subject);
170
+ await this.loadCustomFields.perform(subject, true); // Force reload after deletion
131
171
  modal.done();
132
172
  } catch (error) {
133
173
  this.notifications.serverError(error);
@@ -151,7 +191,7 @@ export default class CustomFieldsManagerComponent extends Component {
151
191
 
152
192
  try {
153
193
  await customField.destroyRecord();
154
- await this.loadCustomFields.perform(subject);
194
+ await this.loadCustomFields.perform(subject, true); // Force reload after deletion
155
195
  modal.done();
156
196
  } catch (error) {
157
197
  this.notifications.serverError(error);
@@ -161,6 +201,13 @@ export default class CustomFieldsManagerComponent extends Component {
161
201
  });
162
202
  }
163
203
 
204
+ @action onTabChange(subject) {
205
+ // Ensure custom fields are loaded when switching tabs
206
+ if (subject && (!subject.groups || subject.groups.length === 0)) {
207
+ this.loadCustomFields.perform(subject);
208
+ }
209
+ }
210
+
164
211
  #addCustomFieldToGroup(subject, customField, group) {
165
212
  this.#updateGroupOnSubject(subject, group.id, (g) => {
166
213
  const current = isArray(g.customFields) ? g.customFields : [];
@@ -207,12 +254,8 @@ export default class CustomFieldsManagerComponent extends Component {
207
254
  #updateSubject(subject, updater) {
208
255
  if (!subject) return;
209
256
 
210
- // Try to locate by object identity first; fallback to stable keys
211
- const idKey = 'id' in subject ? 'id' : 'model' in subject ? 'model' : null;
212
- const idx = this.subjects.findIndex((s) => {
213
- if (s === subject) return true;
214
- return idKey ? s?.[idKey] === subject?.[idKey] : false;
215
- });
257
+ // Find by model property since tab objects have extra properties
258
+ const idx = this.subjects.findIndex((s) => s?.model === subject?.model);
216
259
 
217
260
  if (idx === -1) return;
218
261
 
@@ -23,7 +23,7 @@ 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, extension = 'core' }) {
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(() => {
@@ -0,0 +1,48 @@
1
+ <div class="fleetbase-pill" ...attributes>
2
+ <a href="javascript:;" class="flex flex-row space-x-2 {{@anchorClass}}" {{on "click" this.handleClick}}>
3
+ <div class="relative shrink-0 {{@imageWrapperClass}}">
4
+ {{#if (has-block "image")}}
5
+ {{yield @resource to="image"}}
6
+ {{else}}
7
+ <Image
8
+ src={{@imageSrc}}
9
+ @fallbackSrc={{or @imageFallback (config (concat "defaultValues." @fallbackImageType)) (config "defaultValues.placeholderImage")}}
10
+ width={{or @imageSize @imageWidth "28"}}
11
+ height={{or @imageSize @imageHeight "28"}}
12
+ class="w-7 h-7 rounded-full ring-2 ring-gray-800 dark:ring-gray-700 shadow transition-shadow hover:shadow-md focus:shadow-md {{@imageClass}}"
13
+ alt={{or @imageAlt this.resourceName}}
14
+ />
15
+ {{#if @showOnlineIndicator}}
16
+ <FaIcon
17
+ @icon="circle"
18
+ @size="2xs"
19
+ class="absolute left-0 top-0 h-2 w-2 {{if (get @resource (or @onlinePath 'online')) 'text-green-500' 'text-yellow-200'}} {{@onlineIndicatorClass}}"
20
+ />
21
+ {{/if}}
22
+ {{yield @resource to="image"}}
23
+ {{/if}}
24
+ </div>
25
+ <div class={{@contentWrapperClass}}>
26
+ {{#if (has-block)}}
27
+ {{yield @resource}}
28
+ {{else}}
29
+ <div class="text-sm {{@titleClass}}">{{n-a @title this.resourceName}}</div>
30
+ {{#if @subtitle}}
31
+ <div class="text-xs text-gray-400 dark:text-gray-500 {{@subtitleClass}}">{{n-a @subtitle}}</div>
32
+ {{/if}}
33
+ {{yield @resource}}
34
+ {{/if}}
35
+ </div>
36
+ {{#if (has-block "tooltip")}}
37
+ <Attach::Tooltip @class="clean" @animation="scale" @placement={{or @tooltipPosition "top"}}>
38
+ <InputInfo>
39
+ {{yield @resource to="tooltip"}}
40
+ </InputInfo>
41
+ </Attach::Tooltip>
42
+ {{else if @tooltipComponent}}
43
+ <Attach::Tooltip @class="clean" @animation="scale" @placement={{or @tooltipPosition "top"}}>
44
+ {{component @tooltipComponent}}
45
+ </Attach::Tooltip>
46
+ {{/if}}
47
+ </a>
48
+ </div>
@@ -0,0 +1,31 @@
1
+ import Component from '@glimmer/component';
2
+ import { action, get } from '@ember/object';
3
+ import getModelName from '@fleetbase/ember-core/utils/get-model-name';
4
+
5
+ export default class PillComponent extends Component {
6
+ /* eslint-disable ember/no-get */
7
+ get resourceName() {
8
+ const record = this.args.resource;
9
+ if (!record) return 'resource';
10
+
11
+ return (
12
+ get(record, this.args.namePath ?? 'name') ??
13
+ get(record, 'display_name') ??
14
+ get(record, 'displayName') ??
15
+ get(record, 'tracking') ??
16
+ get(record, 'public_id') ??
17
+ getModelName(record)
18
+ );
19
+ }
20
+
21
+ @action handleClick() {
22
+ console.log('handleClick called!', ...arguments);
23
+ if (typeof this.args.onClick === 'function') {
24
+ if (this.args.resource) {
25
+ this.args.onClick(this.args.resource, ...arguments);
26
+ } else {
27
+ this.args.onClick(...arguments);
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,6 @@
1
+ import { helper } from '@ember/component/helper';
2
+ import isEmptyObject from '@fleetbase/ember-core/utils/is-empty-object';
3
+
4
+ export default helper(function isObjectEmpty([subject]) {
5
+ return isEmptyObject(subject);
6
+ });
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-ui/components/pill';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-ui/helpers/is-object-empty';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-ui",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
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",