Skip to content

Commit 052a844

Browse files
authored
Merge pull request #90 from fleetbase/dev-v0.3.8
v0.3.8
2 parents 2482434 + d94248c commit 052a844

File tree

10 files changed

+184
-11
lines changed

10 files changed

+184
-11
lines changed

addon/components/custom-fields-manager.js

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export default class CustomFieldsManagerComponent extends Component {
3333
super(...arguments);
3434
this.subjects = subjects ?? [];
3535
next(() => {
36-
if (!this.subjects) return;
36+
if (!this.subjects || this.subjects.length === 0) return;
37+
// Load the first subject immediately
3738
this.loadCustomFields.perform(this.subjects[0]);
3839
});
3940
}
@@ -51,7 +52,44 @@ export default class CustomFieldsManagerComponent extends Component {
5152
}
5253
}
5354

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+
5490
@task *loadCustomFields(subject) {
91+
if (!subject) return;
92+
5593
try {
5694
const company = yield this.currentUser.loadCompany();
5795
const customFieldsManager = yield this.customFieldsRegistry.loadSubjectCustomFields.perform(company, {
@@ -60,12 +98,14 @@ export default class CustomFieldsManagerComponent extends Component {
6098
fieldFor: subject.type,
6199
},
62100
});
101+
63102
this.#updateSubject(subject, (s) => {
64103
return { ...s, groups: customFieldsManager.customFieldGroups };
65104
});
66105

67106
return customFieldsManager;
68107
} catch (err) {
108+
console.error(`❌ Failed to load custom fields for ${subject.model}:`, err);
69109
this.notifications.serverError(err);
70110
}
71111
}
@@ -127,7 +167,7 @@ export default class CustomFieldsManagerComponent extends Component {
127167

128168
try {
129169
await group.destroyRecord();
130-
await this.loadCustomFields.perform(subject);
170+
await this.loadCustomFields.perform(subject, true); // Force reload after deletion
131171
modal.done();
132172
} catch (error) {
133173
this.notifications.serverError(error);
@@ -151,7 +191,7 @@ export default class CustomFieldsManagerComponent extends Component {
151191

152192
try {
153193
await customField.destroyRecord();
154-
await this.loadCustomFields.perform(subject);
194+
await this.loadCustomFields.perform(subject, true); // Force reload after deletion
155195
modal.done();
156196
} catch (error) {
157197
this.notifications.serverError(error);
@@ -161,6 +201,13 @@ export default class CustomFieldsManagerComponent extends Component {
161201
});
162202
}
163203

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+
164211
#addCustomFieldToGroup(subject, customField, group) {
165212
this.#updateGroupOnSubject(subject, group.id, (g) => {
166213
const current = isArray(g.customFields) ? g.customFields : [];
@@ -207,12 +254,8 @@ export default class CustomFieldsManagerComponent extends Component {
207254
#updateSubject(subject, updater) {
208255
if (!subject) return;
209256

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);
216259

217260
if (idx === -1) return;
218261

addon/components/dashboard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default class DashboardComponent extends Component {
2323
* Creates an instance of DashboardComponent.
2424
* @memberof DashboardComponent
2525
*/
26-
constructor(owner, { defaultDashboardId = 'dashboard', defaultDashboardName = 'Default Dashboard', showPanelWhenZeroWidgets = false, extension = 'core' }) {
26+
constructor(owner, { defaultDashboardId = 'dashboard', defaultDashboardName = 'Default Dashboard', showPanelWhenZeroWidgets = false, extension = 'core' } = {}) {
2727
super(...arguments);
2828
this.dashboard.reset(); // ensure service is reset when re-rendering
2929
next(() => {

addon/components/pill.hbs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

addon/components/pill.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

addon/helpers/is-object-empty.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});

app/components/pill.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from '@fleetbase/ember-ui/components/pill';

app/helpers/is-object-empty.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from '@fleetbase/ember-ui/helpers/is-object-empty';

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fleetbase/ember-ui",
3-
"version": "0.3.7",
3+
"version": "0.3.8",
44
"description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.",
55
"keywords": [
66
"fleetbase-ui",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from 'dummy/tests/helpers';
3+
import { render } from '@ember/test-helpers';
4+
import { hbs } from 'ember-cli-htmlbars';
5+
6+
module('Integration | Component | pill', function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test('it renders', async function (assert) {
10+
// Set any properties with this.set('myProperty', 'value');
11+
// Handle any actions with this.set('myAction', function(val) { ... });
12+
13+
await render(hbs`<Pill />`);
14+
15+
assert.dom().hasText('');
16+
17+
// Template block usage:
18+
await render(hbs`
19+
<Pill>
20+
template block text
21+
</Pill>
22+
`);
23+
24+
assert.dom().hasText('template block text');
25+
});
26+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from 'dummy/tests/helpers';
3+
import { render } from '@ember/test-helpers';
4+
import { hbs } from 'ember-cli-htmlbars';
5+
6+
module('Integration | Helper | is-object-empty', function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
// TODO: Replace this with your real tests.
10+
test('it renders', async function (assert) {
11+
this.set('inputValue', '1234');
12+
13+
await render(hbs`{{is-object-empty this.inputValue}}`);
14+
15+
assert.dom().hasText('1234');
16+
});
17+
});

0 commit comments

Comments
 (0)