@fleetbase/ember-ui 0.3.7 → 0.3.9

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(() => {
@@ -1,24 +1,9 @@
1
1
  <Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
2
2
  <div class="modal-body-container">
3
- <div class="bg-blue-50 rounded shadow-sm border-l-4 border-blue-400 p-4 mb-5">
4
- <div class="flex">
5
- <div class="flex-shrink-0">
6
- <svg class="h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
7
- <path
8
- fill-rule="evenodd"
9
- d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
10
- clip-rule="evenodd"
11
- ></path>
12
- </svg>
13
- </div>
14
- <div class="ml-3 flex items-center">
15
- <span class="font-extrabold text-blue-800">Verify your email address</span>
16
- </div>
17
- </div>
18
- <div class="py-3">
19
- <p class="text-blue-700">Re-verify your email address and click Send to continue.</p>
20
- </div>
21
- </div>
3
+ <InfoBlock @type="info" @icon="lightbulb" @iconSize="lg" class="mb-5">
4
+ <div class="font-extrabold">Verify your email address.</div>
5
+ <div>Re-verify your email address and click Send to continue.</div>
6
+ </InfoBlock>
22
7
 
23
8
  <InputGroup
24
9
  @type="email"
@@ -1,6 +1,6 @@
1
1
  <CoordinatesInput
2
2
  @value={{if @locationProperty (get @model @locationProperty) @model.location}}
3
- @onChange={{this.updatePlaceCoordinates}}
3
+ @onChange={{this.updateCoordinates}}
4
4
  @onGeocode={{this.onAutocomplete}}
5
5
  @onUpdatedFromMap={{perform this.reverseGeocode}}
6
6
  @onInit={{this.setCoordinatesInput}}
@@ -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
+ }
@@ -2,11 +2,15 @@
2
2
  <div class="tab-navigation {{@containerClass}}" data-style={{or @style "github"}} data-size={{or @size "md"}}>
3
3
  <div class="tab-list justify-between {{@tablistClass}}" role="tablist">
4
4
  <div class="flex flex-row items-center" role="tablist">
5
+ {{#if (has-block "title")}}
6
+ <div id="tab-navigation-title" class="tab-navigation-title {{@tabTitleWrapperClass}}">
7
+ {{yield to="title"}}
8
+ </div>
9
+ {{/if}}
5
10
  {{#if (has-block "tabs")}}
6
11
  {{yield this to="tabs"}}
7
12
  {{else if @tabs}}
8
13
  {{#each this.enhancedTabs as |tab|}}
9
-
10
14
  {{#if tab.route}}
11
15
  <LinkTo
12
16
  @route={{tab.route}}
@@ -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
+ });
@@ -2053,7 +2053,7 @@ body[data-theme='light'] .next-table-wrapper table tfoot tr td {
2053
2053
  }
2054
2054
 
2055
2055
  .field-info-container > .field-name {
2056
- @apply font-semibold text-gray-700;
2056
+ @apply text-[11px] tracking-wide uppercase font-semibold text-gray-700;
2057
2057
  }
2058
2058
 
2059
2059
  .field-info-container > .field-value {
@@ -2061,7 +2061,7 @@ body[data-theme='light'] .next-table-wrapper table tfoot tr td {
2061
2061
  }
2062
2062
 
2063
2063
  body[data-theme='dark'] .field-info-container > .field-name {
2064
- @apply text-gray-400;
2064
+ @apply text-gray-500;
2065
2065
  }
2066
2066
 
2067
2067
  body[data-theme='dark'] .field-info-container > .field-value {
@@ -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.9",
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",