@headless-adminapp/app 1.0.2 → 1.1.2

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.
@@ -16,7 +16,7 @@ function DataResolver() {
16
16
  const { filter, maxRecords = MAX_RECORDS } = (0, useBoardColumnConfig_1.useBoardColumnConfig)();
17
17
  const setState = (0, mutable_1.useContextSetValue)(context_1.BoardColumnContext);
18
18
  const [search] = (0, hooks_1.useDebouncedValue)(searchText, 500);
19
- const queryKey = (0, useRetriveRecords_1.useRetrieveRecordsKey)({
19
+ const { fetchNextPage, data, hasNextPage, isFetching, isFetchingNextPage, queryKey, } = (0, useRetriveRecords_1.useRetriveRecords)({
20
20
  columns,
21
21
  expand,
22
22
  filter,
@@ -26,18 +26,11 @@ function DataResolver() {
26
26
  sorting,
27
27
  });
28
28
  (0, useRetriveRecords_1.useClearDataExceptFirstPage)(queryKey);
29
- const { fetchNextPage, data, hasNextPage, isFetching, isFetchingNextPage } = (0, useRetriveRecords_1.useRetriveRecords)(queryKey, {
30
- columns,
31
- expand,
32
- filter,
33
- maxRecords,
34
- schema,
35
- search,
36
- sorting,
37
- });
38
29
  (0, react_1.useEffect)(() => {
39
30
  setState({
40
- fetchNextPage: fetchNextPage,
31
+ fetchNextPage: () => {
32
+ fetchNextPage().catch(console.error);
33
+ },
41
34
  });
42
35
  }, [fetchNextPage, setState]);
43
36
  (0, react_1.useEffect)(() => {
package/board/types.d.ts CHANGED
@@ -44,4 +44,8 @@ export interface BoardConfig<S extends SchemaAttributes = SchemaAttributes> {
44
44
  };
45
45
  columnConfigs: BoardColumnConfig[];
46
46
  PreviewComponent: BoardColumnCardPreviewFC;
47
+ HeaderComponent?: FC;
48
+ emptyMessage?: string;
49
+ minColumnWidth?: number;
50
+ maxColumnWidth?: number;
47
51
  }
@@ -1,6 +1,13 @@
1
- import { EntityMainGridCommandItemExperience } from '@headless-adminapp/core/experience/view';
1
+ import { EntityMainGridCommandContext, EntityMainGridCommandItemExperience } from '@headless-adminapp/core/experience/view';
2
2
  import { Localized } from '@headless-adminapp/core/types';
3
3
  import { Icon } from '@headless-adminapp/icons';
4
+ export declare namespace EnabledRules {
5
+ function HasCreatePermisssion(context: EntityMainGridCommandContext): boolean;
6
+ function HasUpdatePermission(context: EntityMainGridCommandContext): boolean;
7
+ function HasDeletePermission(context: EntityMainGridCommandContext): boolean;
8
+ function HasSingleRecordSelected(context: EntityMainGridCommandContext): boolean;
9
+ function HasAtLeastOneRecordSelected(context: EntityMainGridCommandContext): boolean;
10
+ }
4
11
  export declare namespace ViewCommandBuilder {
5
12
  function createNewRecordCommand({ Icon, text, localizedTexts, }: {
6
13
  Icon: Icon;
@@ -61,3 +68,9 @@ export declare namespace ViewCommandBuilder {
61
68
  };
62
69
  }): EntityMainGridCommandItemExperience;
63
70
  }
71
+ export declare function exportRecordsToExcel(context: EntityMainGridCommandContext): Promise<void>;
72
+ export declare function exportRecordsToCSV(context: EntityMainGridCommandContext): Promise<void>;
73
+ export declare function processDeleteRecordRequest(context: EntityMainGridCommandContext, { stringSet, localizedStringSet, }: {
74
+ stringSet: ViewCommandBuilder.DeleteRecordCommandStringSet;
75
+ localizedStringSet?: Localized<ViewCommandBuilder.DeleteRecordCommandStringSet>;
76
+ }): Promise<void>;
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ViewCommandBuilder = void 0;
3
+ exports.ViewCommandBuilder = exports.EnabledRules = void 0;
4
+ exports.exportRecordsToExcel = exportRecordsToExcel;
5
+ exports.exportRecordsToCSV = exportRecordsToCSV;
6
+ exports.processDeleteRecordRequest = processDeleteRecordRequest;
4
7
  const utils_1 = require("../utils");
5
8
  const utils_2 = require("./utils");
6
9
  var EnabledRules;
@@ -25,7 +28,7 @@ var EnabledRules;
25
28
  return context.primaryControl.selectedIds.length > 0;
26
29
  }
27
30
  EnabledRules.HasAtLeastOneRecordSelected = HasAtLeastOneRecordSelected;
28
- })(EnabledRules || (EnabledRules = {}));
31
+ })(EnabledRules || (exports.EnabledRules = EnabledRules = {}));
29
32
  var ViewCommandBuilder;
30
33
  (function (ViewCommandBuilder) {
31
34
  function createNewRecordCommand({ Icon, text, localizedTexts, }) {
@@ -92,42 +95,10 @@ var ViewCommandBuilder;
92
95
  danger: true,
93
96
  isContextMenu: true,
94
97
  onClick: async (context) => {
95
- const recordIds = context.primaryControl.selectedIds;
96
- if (!recordIds.length) {
97
- return;
98
- }
99
- const localizeSelector = (0, utils_2.createLocalizedSelector)(stringSet, localizedStringSet, context.locale.language);
100
- try {
101
- const confirmResult = await context.utility.openConfirmDialog({
102
- title: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.confirmation.title)),
103
- text: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.confirmation.text)),
104
- cancelButtonLabel: localizeSelector((s) => s.confirmation.buttonCancel),
105
- confirmButtonLabel: localizeSelector((s) => s.confirmation.buttonConfirm),
106
- });
107
- if (!confirmResult?.confirmed) {
108
- return;
109
- }
110
- context.utility.showProgressIndicator((0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.status.deleting)) + '...');
111
- for (const recordId of recordIds) {
112
- await context.dataService.deleteRecord(context.primaryControl.logicalName, recordId);
113
- }
114
- context.utility.showNotification({
115
- title: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.successNotification.title)),
116
- text: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.successNotification.text)),
117
- type: 'success',
118
- });
119
- context.primaryControl.refresh();
120
- }
121
- catch (error) {
122
- context.utility.showNotification({
123
- title: localizeSelector((s) => s.errorNotification.title),
124
- text: error.message,
125
- type: 'error',
126
- });
127
- }
128
- finally {
129
- context.utility.hideProgressIndicator();
130
- }
98
+ await processDeleteRecordRequest(context, {
99
+ stringSet,
100
+ localizedStringSet,
101
+ });
131
102
  },
132
103
  hidden: [
133
104
  (context) => !EnabledRules.HasAtLeastOneRecordSelected(context),
@@ -161,32 +132,7 @@ var ViewCommandBuilder;
161
132
  text: excel.text,
162
133
  localizedTexts: excel.localizedTexts,
163
134
  onClick: async (context) => {
164
- context.utility.showProgressIndicator('Exporting to Excel...');
165
- try {
166
- const result = await (0, utils_1.retriveRecords)({
167
- columnFilters: context.primaryControl.columnFilter,
168
- dataService: context.dataService,
169
- gridColumns: context.primaryControl.gridColumns,
170
- schema: context.primaryControl.schema,
171
- schemaStore: context.stores.schemaStore,
172
- view: context.primaryControl.view,
173
- search: context.primaryControl.searchText,
174
- extraFilter: context.primaryControl.extraFilter,
175
- sorting: context.primaryControl.sorting,
176
- skip: 0,
177
- limit: 5000,
178
- });
179
- await (0, utils_1.exportRecordsXLS)({
180
- fileName: context.primaryControl.view.name + '.xlsx',
181
- gridColumns: context.primaryControl.gridColumns,
182
- records: result.records,
183
- schema: context.primaryControl.schema,
184
- schemaStore: context.stores.schemaStore,
185
- });
186
- }
187
- finally {
188
- context.utility.hideProgressIndicator();
189
- }
135
+ await exportRecordsToExcel(context);
190
136
  },
191
137
  },
192
138
  {
@@ -194,32 +140,7 @@ var ViewCommandBuilder;
194
140
  text: csv.text,
195
141
  localizedTexts: csv.localizedTexts,
196
142
  onClick: async (context) => {
197
- context.utility.showProgressIndicator('Exporting to CSV...');
198
- try {
199
- const result = await (0, utils_1.retriveRecords)({
200
- columnFilters: context.primaryControl.columnFilter,
201
- dataService: context.dataService,
202
- gridColumns: context.primaryControl.gridColumns,
203
- schema: context.primaryControl.schema,
204
- schemaStore: context.stores.schemaStore,
205
- view: context.primaryControl.view,
206
- search: context.primaryControl.searchText,
207
- extraFilter: context.primaryControl.extraFilter,
208
- sorting: context.primaryControl.sorting,
209
- skip: 0,
210
- limit: 5000,
211
- });
212
- await (0, utils_1.exportRecordsCSV)({
213
- fileName: context.primaryControl.view.name + '.csv',
214
- gridColumns: context.primaryControl.gridColumns,
215
- records: result.records,
216
- schema: context.primaryControl.schema,
217
- schemaStore: context.stores.schemaStore,
218
- });
219
- }
220
- finally {
221
- context.utility.hideProgressIndicator();
222
- }
143
+ await exportRecordsToCSV(context);
223
144
  },
224
145
  },
225
146
  ],
@@ -228,3 +149,88 @@ var ViewCommandBuilder;
228
149
  }
229
150
  ViewCommandBuilder.createExportCommand = createExportCommand;
230
151
  })(ViewCommandBuilder || (exports.ViewCommandBuilder = ViewCommandBuilder = {}));
152
+ async function retrieveFilteredRecords(context) {
153
+ return (0, utils_1.retriveRecords)({
154
+ columnFilters: context.primaryControl.columnFilter,
155
+ dataService: context.dataService,
156
+ gridColumns: context.primaryControl.gridColumns,
157
+ schema: context.primaryControl.schema,
158
+ schemaStore: context.stores.schemaStore,
159
+ view: context.primaryControl.view,
160
+ search: context.primaryControl.searchText,
161
+ extraFilter: context.primaryControl.extraFilter,
162
+ sorting: context.primaryControl.sorting,
163
+ skip: 0,
164
+ limit: 5000,
165
+ });
166
+ }
167
+ async function exportRecordsToExcel(context) {
168
+ context.utility.showProgressIndicator('Exporting to Excel...');
169
+ try {
170
+ const result = await retrieveFilteredRecords(context);
171
+ await (0, utils_1.exportRecordsXLS)({
172
+ fileName: context.primaryControl.view.name + '.xlsx',
173
+ gridColumns: context.primaryControl.gridColumns,
174
+ records: result.records,
175
+ schema: context.primaryControl.schema,
176
+ schemaStore: context.stores.schemaStore,
177
+ });
178
+ }
179
+ finally {
180
+ context.utility.hideProgressIndicator();
181
+ }
182
+ }
183
+ async function exportRecordsToCSV(context) {
184
+ context.utility.showProgressIndicator('Exporting to CSV...');
185
+ try {
186
+ const result = await retrieveFilteredRecords(context);
187
+ await (0, utils_1.exportRecordsCSV)({
188
+ fileName: context.primaryControl.view.name + '.csv',
189
+ gridColumns: context.primaryControl.gridColumns,
190
+ records: result.records,
191
+ schema: context.primaryControl.schema,
192
+ schemaStore: context.stores.schemaStore,
193
+ });
194
+ }
195
+ finally {
196
+ context.utility.hideProgressIndicator();
197
+ }
198
+ }
199
+ async function processDeleteRecordRequest(context, { stringSet, localizedStringSet, }) {
200
+ const recordIds = context.primaryControl.selectedIds;
201
+ if (!recordIds.length) {
202
+ return;
203
+ }
204
+ const localizeSelector = (0, utils_2.createLocalizedSelector)(stringSet, localizedStringSet, context.locale.language);
205
+ try {
206
+ const confirmResult = await context.utility.openConfirmDialog({
207
+ title: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.confirmation.title)),
208
+ text: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.confirmation.text)),
209
+ cancelButtonLabel: localizeSelector((s) => s.confirmation.buttonCancel),
210
+ confirmButtonLabel: localizeSelector((s) => s.confirmation.buttonConfirm),
211
+ });
212
+ if (!confirmResult?.confirmed) {
213
+ return;
214
+ }
215
+ context.utility.showProgressIndicator((0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.status.deleting)) + '...');
216
+ for (const recordId of recordIds) {
217
+ await context.dataService.deleteRecord(context.primaryControl.logicalName, recordId);
218
+ }
219
+ context.utility.showNotification({
220
+ title: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.successNotification.title)),
221
+ text: (0, utils_2.plurialize)(recordIds.length, localizeSelector((s) => s.successNotification.text)),
222
+ type: 'success',
223
+ });
224
+ context.primaryControl.refresh();
225
+ }
226
+ catch (error) {
227
+ context.utility.showNotification({
228
+ title: localizeSelector((s) => s.errorNotification.title),
229
+ text: error.message,
230
+ type: 'error',
231
+ });
232
+ }
233
+ finally {
234
+ context.utility.hideProgressIndicator();
235
+ }
236
+ }
@@ -5,12 +5,13 @@ import type { SchemaAttributes } from '@headless-adminapp/core/schema';
5
5
  export declare namespace DefineFormExperience {
6
6
  export type Experience<S extends SchemaAttributes = SchemaAttributes> = (Pick<$FormExperience<S>, 'useHookFn' | 'relatedItems' | 'processFlow' | 'cloneAttributes' | 'defaultValues' | 'includeAttributes' | 'headerControls'> & {
7
7
  tabs: Tab<S>[];
8
- }) | (keyof S)[];
8
+ }) | SectionControl<S>[];
9
9
  export function resolveExperience<S extends SchemaAttributes = SchemaAttributes>(v: Experience<S>): $FormExperience<S>;
10
- type Tab<S extends SchemaAttributes = SchemaAttributes> = Pick<$Tab<S>, 'label' | 'localizedLabels' | 'name'> & ({
11
- tabColumns: TabColumn<S>[];
10
+ type Tab<S extends SchemaAttributes = SchemaAttributes> = Pick<$Tab<S>, 'label' | 'localizedLabels' | 'name'> & {
12
11
  columnCount?: $Tab<S>['columnCount'];
13
12
  columnWidths?: $Tab<S>['columnWidths'];
13
+ } & ({
14
+ tabColumns: TabColumn<S>[];
14
15
  } | {
15
16
  sections: Section<S>[];
16
17
  } | {
@@ -41,13 +41,12 @@ var DefineFormExperience;
41
41
  name: v.name,
42
42
  localizedLabels: v.localizedLabels,
43
43
  };
44
+ tab.columnCount = v.columnCount ?? 2;
45
+ tab.columnWidths = v.columnWidths;
44
46
  if ('tabColumns' in v) {
45
- tab.columnCount = v.columnCount ?? 2;
46
- tab.columnWidths = v.columnWidths;
47
47
  tab.tabColumns = v.tabColumns.map(resolveTabColumn);
48
48
  }
49
49
  else if ('sections' in v) {
50
- tab.columnCount = 2;
51
50
  tab.tabColumns = [
52
51
  {
53
52
  sections: v.sections.map(resolveSection),
@@ -55,7 +54,6 @@ var DefineFormExperience;
55
54
  ];
56
55
  }
57
56
  else if ('controls' in v) {
58
- tab.columnCount = 2;
59
57
  tab.tabColumns = [
60
58
  {
61
59
  sections: [
@@ -142,13 +142,14 @@ class SchemaExperienceBuilder extends BaseSchemaExperienceBuilder {
142
142
  {
143
143
  name: 'general',
144
144
  label: 'General',
145
- columnCount: 1,
145
+ columnCount: 2,
146
146
  tabColumns: [
147
147
  {
148
148
  sections: [
149
149
  {
150
150
  name: 'general',
151
151
  label: 'General',
152
+ hideLabel: true,
152
153
  controls: [
153
154
  {
154
155
  type: 'standard',
@@ -244,7 +245,7 @@ class SchemaExperienceBuilder extends BaseSchemaExperienceBuilder {
244
245
  }
245
246
  return {
246
247
  logicalName: this.logicalName,
247
- icon: experience.icon,
248
+ Icon: experience.Icon,
248
249
  defaultQuickCreateFormId: defaultQuickCreateFormId,
249
250
  defaultFormId,
250
251
  defaultViewId,
package/builders/utils.js CHANGED
@@ -35,7 +35,7 @@ const getHeaders = (schema, gridColumns, schemaStore) => {
35
35
  const lookupSchema = schemaStore.getSchema(entity);
36
36
  return `${lookupSchema.attributes[field]?.label} (${schema.attributes[lookup]?.label})`;
37
37
  }
38
- return schema.attributes[column.name]?.label;
38
+ return column.label ?? schema.attributes[column.name]?.label;
39
39
  });
40
40
  return headers;
41
41
  };
@@ -16,7 +16,7 @@ export interface CalendarEventsResolverFnOptions<SA extends SchemaAttributes = S
16
16
  auth: AuthSession | null;
17
17
  }
18
18
  export type CalendarEventsResolverFn<SA extends SchemaAttributes = SchemaAttributes> = (options: CalendarEventsResolverFnOptions<SA>) => Promise<CalendarEvent[]>;
19
- export type CalendarEventResolverFn<T extends CalendarEvent = CalendarEvent> = (id: string) => Promise<T>;
19
+ export type CalendarEventResolverFn<T extends CalendarEvent = CalendarEvent> = (id: string) => Promise<Partial<T>>;
20
20
  export type CalendarEventSaveData<SA1 extends SchemaAttributes = SchemaAttributes, SA2 extends SchemaAttributes = SchemaAttributes> = Omit<CalendarEvent, 'id'> & {
21
21
  id?: CalendarEvent['id'];
22
22
  } & InferredSchemaType<SA1> & InferredSchemaType<SA2>;
@@ -38,4 +38,8 @@ export interface CalendarConfig<SA1 extends SchemaAttributes = SchemaAttributes,
38
38
  title: string;
39
39
  description: string;
40
40
  eventLabel: string;
41
+ disableCreate?: boolean;
42
+ disableEdit?: boolean;
43
+ disableDelete?: boolean;
44
+ disableAllDay?: boolean;
41
45
  }
@@ -36,6 +36,7 @@ interface BaseCommandState {
36
36
  iconPosition?: 'before' | 'after';
37
37
  danger?: boolean;
38
38
  hidden?: boolean;
39
+ appearance?: 'subtle' | 'colored';
39
40
  }
40
41
  interface ActionableCommandState {
41
42
  onClick?: () => void;
@@ -27,6 +27,7 @@ function transformCommand(command, handlerContext) {
27
27
  isQuickAction: command.isQuickAction,
28
28
  danger: command.danger,
29
29
  iconPosition: command.iconPosition,
30
+ appearance: command.appearance,
30
31
  ...transformedProperties(command, handlerContext),
31
32
  };
32
33
  case 'button':
@@ -37,6 +38,7 @@ function transformCommand(command, handlerContext) {
37
38
  danger: command.danger,
38
39
  iconPosition: command.iconPosition,
39
40
  localizedTexts: command.localizedText,
41
+ appearance: command.appearance,
40
42
  ...transformedProperties(command, handlerContext),
41
43
  };
42
44
  case 'menu':
@@ -46,6 +48,7 @@ function transformCommand(command, handlerContext) {
46
48
  danger: command.danger,
47
49
  localizedTexts: command.localizedTexts,
48
50
  iconPosition: command.iconPosition,
51
+ appearance: command.appearance,
49
52
  ...transformedProperties(command, handlerContext),
50
53
  items: command.items.map((item) => transformMenuItems(item, handlerContext)),
51
54
  };
@@ -4,7 +4,7 @@ import { ISchemaStore } from '@headless-adminapp/core/store';
4
4
  import { IDataService } from '@headless-adminapp/core/transport';
5
5
  import { Nullable } from '@headless-adminapp/core/types';
6
6
  export declare function getModifiedValues(initialValues: any, values: any, exclude?: string[]): Record<string, any>;
7
- type SaveRecordResult = {
7
+ export type SaveRecordResult = {
8
8
  success: true;
9
9
  recordId: string;
10
10
  } | {
@@ -13,15 +13,14 @@ type SaveRecordResult = {
13
13
  message: string;
14
14
  isError: boolean;
15
15
  };
16
- export interface SaveRecordFnOptions {
16
+ export interface SaveRecordFnOptions<S extends SchemaAttributes = SchemaAttributes> {
17
17
  values: any;
18
- form: Form<SchemaAttributes>;
19
- record: InferredSchemaType<SchemaAttributes> | undefined;
20
- initialValues: Nullable<InferredSchemaType<SchemaAttributes>>;
21
- schema: Schema<SchemaAttributes>;
18
+ form: Form<S>;
19
+ record: InferredSchemaType<S> | undefined;
20
+ initialValues: Nullable<InferredSchemaType<S>>;
21
+ schema: Schema<S>;
22
22
  dataService: IDataService;
23
23
  schemaStore: ISchemaStore;
24
24
  }
25
25
  export type SaveRecordFn = (options: SaveRecordFnOptions) => Promise<SaveRecordResult>;
26
26
  export declare function saveRecord({ values, form, schema, dataService, initialValues, record, schemaStore, }: SaveRecordFnOptions): Promise<SaveRecordResult>;
27
- export {};
@@ -2,56 +2,45 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DataResolver = DataResolver;
4
4
  const react_1 = require("react");
5
+ const hooks_1 = require("../../hooks");
5
6
  const useDebouncedValue_1 = require("../../hooks/useDebouncedValue");
6
7
  const useMetadata_1 = require("../../metadata/hooks/useMetadata");
7
8
  const context_1 = require("../../mutable/context");
8
9
  const useRetriveRecords_1 = require("../../transport/hooks/useRetriveRecords");
9
10
  const context_2 = require("../context");
10
- const hooks_1 = require("../hooks");
11
+ const hooks_2 = require("../hooks");
11
12
  const useGridDisabled_1 = require("../hooks/useGridDisabled");
12
13
  const utils_1 = require("./utils");
13
- const MAX_RECORDS = 10000;
14
14
  function DataResolver() {
15
- const schema = (0, hooks_1.useDataGridSchema)();
16
- const view = (0, hooks_1.useSelectedView)();
17
- const [sorting] = (0, hooks_1.useGridSorting)();
18
- const [searchText] = (0, hooks_1.useSearchText)();
19
- const extraFilter = (0, hooks_1.useGridExtraFilter)();
20
- const [columnFilters] = (0, hooks_1.useGridColumnFilter)();
21
- const gridColumns = (0, hooks_1.useGridColumns)();
22
- const maxRecords = (0, hooks_1.useMaxRecords)() ?? MAX_RECORDS;
23
- const [selectedIds] = (0, hooks_1.useGridSelection)();
15
+ const schema = (0, hooks_2.useDataGridSchema)();
16
+ const view = (0, hooks_2.useSelectedView)();
17
+ const [sorting] = (0, hooks_2.useGridSorting)();
18
+ const [searchText] = (0, hooks_2.useSearchText)();
19
+ const extraFilter = (0, hooks_2.useGridExtraFilter)();
20
+ const [columnFilters] = (0, hooks_2.useGridColumnFilter)();
21
+ const gridColumns = (0, hooks_2.useGridColumns)();
22
+ const maxRecords = (0, hooks_2.useMaxRecords)();
23
+ const [selectedIds] = (0, hooks_2.useGridSelection)();
24
24
  const disabled = (0, useGridDisabled_1.useGridDisabled)();
25
25
  const { schemaStore } = (0, useMetadata_1.useMetadata)();
26
26
  const selectedIdsRef = (0, react_1.useRef)(selectedIds);
27
27
  selectedIdsRef.current = selectedIds;
28
28
  const setState = (0, context_1.useContextSetValue)(context_2.GridContext);
29
29
  const [search] = (0, useDebouncedValue_1.useDebouncedValue)(searchText, 500);
30
- const columns = (0, react_1.useMemo)(() => {
31
- const set = new Set([
32
- ...gridColumns.filter((x) => !x.expandedKey).map((x) => x.name),
33
- schema.primaryAttribute,
34
- ]);
35
- if (schema.avatarAttribute) {
36
- set.add(schema.avatarAttribute);
37
- }
38
- return Array.from(set);
39
- }, [gridColumns, schema.avatarAttribute, schema.primaryAttribute]);
40
- const expand = (0, react_1.useMemo)(() => (0, utils_1.collectExpandedKeys)(gridColumns), [gridColumns]);
30
+ const isMobile = (0, hooks_1.useIsMobile)();
31
+ const columns = (0, react_1.useMemo)(() => isMobile
32
+ ? (0, utils_1.collectCardColumns)({ cardView: view.experience.card, schema })
33
+ : (0, utils_1.collectGridColumns)({
34
+ gridColumns,
35
+ schema,
36
+ }), [isMobile, view.experience.card, schema, gridColumns]);
37
+ const expand = (0, react_1.useMemo)(() => isMobile
38
+ ? (0, utils_1.collectCardExpandedKeys)({ cardView: view.experience.card })
39
+ : (0, utils_1.collectExpandedKeys)(gridColumns), [gridColumns, isMobile, view.experience.card]);
41
40
  const filter = (0, react_1.useMemo)(() => {
42
41
  return (0, utils_1.mergeConditions)(schema, view.experience.filter, extraFilter, columnFilters, schemaStore);
43
42
  }, [columnFilters, extraFilter, schema, schemaStore, view.experience.filter]);
44
- const queryKey = (0, useRetriveRecords_1.useRetrieveRecordsKey)({
45
- columns,
46
- expand,
47
- filter,
48
- maxRecords,
49
- schema,
50
- search,
51
- sorting,
52
- });
53
- (0, useRetriveRecords_1.useClearDataExceptFirstPage)(queryKey);
54
- const { fetchNextPage, data, hasNextPage, isFetching, isFetchingNextPage } = (0, useRetriveRecords_1.useRetriveRecords)(queryKey, {
43
+ const { fetchNextPage, data, hasNextPage, isFetching, isFetchingNextPage, queryKey, } = (0, useRetriveRecords_1.useRetriveRecords)({
55
44
  columns,
56
45
  expand,
57
46
  filter,
@@ -61,9 +50,12 @@ function DataResolver() {
61
50
  sorting,
62
51
  disabled,
63
52
  });
53
+ (0, useRetriveRecords_1.useClearDataExceptFirstPage)(queryKey);
64
54
  (0, react_1.useEffect)(() => {
65
55
  setState({
66
- fetchNextPage: () => fetchNextPage,
56
+ fetchNextPage: () => {
57
+ fetchNextPage().catch(console.error);
58
+ },
67
59
  });
68
60
  }, [fetchNextPage, setState]);
69
61
  (0, react_1.useEffect)(() => {
@@ -5,12 +5,12 @@ const utils_1 = require("../../locale/utils");
5
5
  function transformViewColumns(logicalName, columns, schemaStore, language) {
6
6
  const schema = schemaStore.getSchema(logicalName);
7
7
  return columns
8
- .map(column => {
8
+ .map((column) => {
9
9
  const attribute = schema.attributes[column.name];
10
10
  if (!attribute) {
11
11
  return null;
12
12
  }
13
- const label = (0, utils_1.localizedLabel)(language, attribute);
13
+ const label = column.label ?? (0, utils_1.localizedLabel)(language, attribute);
14
14
  if (column.expandedKey) {
15
15
  if (attribute.type !== 'lookup') {
16
16
  return null;
@@ -1,4 +1,4 @@
1
- import { ColumnCondition } from '@headless-adminapp/core/experience/view';
1
+ import { CardView, ColumnCondition } from '@headless-adminapp/core/experience/view';
2
2
  import { Schema, SchemaAttributes } from '@headless-adminapp/core/schema';
3
3
  import { ISchemaStore } from '@headless-adminapp/core/store';
4
4
  import { Filter } from '@headless-adminapp/core/transport';
@@ -6,3 +6,14 @@ import { TransformedViewColumn } from '../context';
6
6
  export declare function transformColumnFilter<S extends SchemaAttributes = SchemaAttributes>(filter: Partial<Record<string, ColumnCondition>>, schema: Schema<S>, schemaStore: ISchemaStore): Record<string, ColumnCondition> | null;
7
7
  export declare function mergeConditions<S extends SchemaAttributes = SchemaAttributes>(schema: Schema<S>, filter: Filter | null | undefined, extraFilter: Filter | null | undefined, columnFilters: Partial<Record<string, ColumnCondition>> | undefined, schemaStore: ISchemaStore): Filter | null;
8
8
  export declare function collectExpandedKeys(columns: TransformedViewColumn<SchemaAttributes>[]): Record<string, string[]>;
9
+ export declare function collectGridColumns<S extends SchemaAttributes = SchemaAttributes>({ gridColumns, schema, }: {
10
+ gridColumns: TransformedViewColumn<S>[];
11
+ schema: Schema<S>;
12
+ }): (keyof S)[];
13
+ export declare function collectCardColumns<S extends SchemaAttributes = SchemaAttributes>({ cardView, schema, }: {
14
+ cardView: CardView<S>;
15
+ schema: Schema<S>;
16
+ }): string[];
17
+ export declare function collectCardExpandedKeys<S extends SchemaAttributes = SchemaAttributes>({ cardView }: {
18
+ cardView: CardView<S>;
19
+ }): Record<string, string[]> | undefined;
@@ -6,6 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.transformColumnFilter = transformColumnFilter;
7
7
  exports.mergeConditions = mergeConditions;
8
8
  exports.collectExpandedKeys = collectExpandedKeys;
9
+ exports.collectGridColumns = collectGridColumns;
10
+ exports.collectCardColumns = collectCardColumns;
11
+ exports.collectCardExpandedKeys = collectCardExpandedKeys;
9
12
  const dayjs_1 = __importDefault(require("dayjs"));
10
13
  function transformColumnFilter(filter, schema, schemaStore) {
11
14
  const transformedResult = Object.entries(filter).reduce((acc, [id, value]) => {
@@ -122,3 +125,42 @@ function collectExpandedKeys(columns) {
122
125
  return acc;
123
126
  }, {});
124
127
  }
128
+ function collectGridColumns({ gridColumns, schema, }) {
129
+ const set = new Set([
130
+ ...gridColumns.filter((x) => !x.expandedKey).map((x) => x.name),
131
+ schema.primaryAttribute,
132
+ ]);
133
+ if (schema.avatarAttribute) {
134
+ set.add(schema.avatarAttribute);
135
+ }
136
+ return Array.from(set);
137
+ }
138
+ function collectCardColumns({ cardView, schema, }) {
139
+ const set = new Set([
140
+ cardView.primaryColumn,
141
+ ...(cardView.secondaryColumns
142
+ ?.filter((x) => !x.expandedKey)
143
+ .map((x) => x.name) ?? []),
144
+ ...(cardView.rightColumn?.map((x) => x.name) ?? []),
145
+ schema.primaryAttribute,
146
+ ]);
147
+ if (cardView.showAvatar &&
148
+ (cardView.avatarColumn || schema.avatarAttribute)) {
149
+ set.add((cardView.avatarColumn ?? schema.avatarAttribute));
150
+ }
151
+ return Array.from(set);
152
+ }
153
+ function collectCardExpandedKeys({ cardView }) {
154
+ return cardView.secondaryColumns
155
+ ?.filter((x) => x.expandedKey)
156
+ .reduce((acc, x) => {
157
+ const name = x.name;
158
+ if (!acc[name]) {
159
+ acc[name] = [];
160
+ }
161
+ if (!acc[name].includes(x.expandedKey)) {
162
+ acc[name].push(x.expandedKey);
163
+ }
164
+ return acc;
165
+ }, {});
166
+ }
@@ -1,3 +1,6 @@
1
- import { InsightsState } from '@headless-adminapp/core/experience/insights';
2
- import { SchemaAttributes } from '@headless-adminapp/core/schema';
1
+ import { InsightConfig, InsightsState } from '@headless-adminapp/core/experience/insights';
2
+ import { InferredSchemaType, SchemaAttributes } from '@headless-adminapp/core/schema';
3
3
  export declare function useInsightsState<S extends SchemaAttributes = SchemaAttributes>(): InsightsState<S>;
4
+ export declare function useInsightConfig<S extends SchemaAttributes = SchemaAttributes>(): InsightConfig<S>;
5
+ export declare function useInsightFilterValues<S extends SchemaAttributes = SchemaAttributes>(): InferredSchemaType<S>;
6
+ export declare function useRefreshEventListener(callback: () => any): void;
@@ -1,8 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useInsightsState = useInsightsState;
4
+ exports.useInsightConfig = useInsightConfig;
5
+ exports.useInsightFilterValues = useInsightFilterValues;
6
+ exports.useRefreshEventListener = useRefreshEventListener;
7
+ const react_1 = require("react");
4
8
  const mutable_1 = require("../../mutable");
5
9
  const context_1 = require("../context");
6
10
  function useInsightsState() {
7
11
  return (0, mutable_1.useContextSelector)(context_1.InsightsContext, (state) => state);
8
12
  }
13
+ function useInsightConfig() {
14
+ return (0, mutable_1.useContextSelector)(context_1.InsightsContext, (state) => state.config);
15
+ }
16
+ function useInsightFilterValues() {
17
+ return (0, mutable_1.useContextSelector)(context_1.InsightsContext, (state) => state.filterValues);
18
+ }
19
+ function useRefreshEventListener(callback) {
20
+ const eventManager = (0, mutable_1.useContextSelector)(context_1.InsightsContext, (state) => state.eventManager);
21
+ const callbackRef = (0, react_1.useRef)(callback);
22
+ callbackRef.current = callback;
23
+ (0, react_1.useEffect)(() => {
24
+ const handler = async () => {
25
+ await callbackRef.current();
26
+ };
27
+ eventManager.on('INSIGHT_REFRESH_TRIGGER', handler);
28
+ return () => {
29
+ eventManager.off('INSIGHT_REFRESH_TRIGGER', handler);
30
+ };
31
+ }, [eventManager]);
32
+ }
@@ -20,7 +20,7 @@ const defaultApp = {
20
20
  label: 'Home',
21
21
  link: '/home',
22
22
  type: app_1.PageType.Custom,
23
- icon: icons_1.IconPlaceholder,
23
+ Icon: icons_1.IconPlaceholder,
24
24
  },
25
25
  logo: {},
26
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-adminapp/app",
3
- "version": "1.0.2",
3
+ "version": "1.1.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -36,5 +36,5 @@
36
36
  "uuid": "11.0.3",
37
37
  "yup": "^1.4.0"
38
38
  },
39
- "gitHead": "fc167255beb567e4557fa7024f8db669db8aaa33"
39
+ "gitHead": "8d492b1b7266fbb8bba1124a8dc51311f54f8fcb"
40
40
  }
@@ -131,7 +131,7 @@ class SchemaExperienceStore {
131
131
  async getViewLookupV2(logicalName, viewId) {
132
132
  const experience = await this.getExperience(logicalName);
133
133
  if (!viewId) {
134
- viewId = experience.defaultViewId;
134
+ viewId = experience.defaultLookupId;
135
135
  }
136
136
  const view = experience.lookups.find((v) => v.id === viewId);
137
137
  if (!view) {
@@ -221,7 +221,7 @@ class SchemaExperienceStore {
221
221
  const schemaMetadatas = this.getSchemaMetadataList();
222
222
  return schemaMetadatas.map((schemaMetadata) => ({
223
223
  ...schemaMetadata,
224
- icon: this.experiences[schemaMetadata.logicalName]?.icon,
224
+ Icon: this.experiences[schemaMetadata.logicalName]?.Icon,
225
225
  }));
226
226
  }
227
227
  }
@@ -20,6 +20,6 @@ export declare class RestDataService implements IDataService {
20
20
  updateRecord<T>(logicalName: string, id: string, data: Partial<T>): Promise<CreateRecordResult<import("@headless-adminapp/core").Id>>;
21
21
  deleteRecord(logicalName: string, id: string): Promise<void>;
22
22
  retriveAggregate<Q extends Record<string, AggregateAttribute> = Record<string, AggregateAttribute>>(query: AggregateQuery<Q>): Promise<InferredAggregateQueryResult<Q>[]>;
23
- customAction<T = unknown>(_actionName: string, _payload: unknown): Promise<T>;
23
+ customAction<T = unknown>(actionName: string, payload: unknown): Promise<T>;
24
24
  }
25
25
  export {};
@@ -94,8 +94,15 @@ class RestDataService {
94
94
  });
95
95
  return result;
96
96
  }
97
- async customAction(_actionName, _payload) {
98
- throw new Error('Custom action not implemented in RestDataService, to use custom actions, define a custom data service extending RestDataService');
97
+ async customAction(actionName, payload) {
98
+ const result = await this.execute({
99
+ type: 'customAction',
100
+ payload: {
101
+ actionName,
102
+ payload,
103
+ },
104
+ });
105
+ return result;
99
106
  }
100
107
  }
101
108
  exports.RestDataService = RestDataService;
@@ -0,0 +1 @@
1
+ export declare function useCustomActionQuery<T = unknown>(actionName: string, payload: unknown): import("@tanstack/react-query").UseQueryResult<T, Error>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCustomActionQuery = useCustomActionQuery;
4
+ const react_query_1 = require("@tanstack/react-query");
5
+ const useDataService_1 = require("./useDataService");
6
+ function useCustomActionQuery(actionName, payload) {
7
+ const dataService = (0, useDataService_1.useDataService)();
8
+ return (0, react_query_1.useQuery)({
9
+ queryKey: ['data', 'customAction', actionName, payload],
10
+ queryFn: async () => {
11
+ return dataService.customAction(actionName, payload);
12
+ },
13
+ });
14
+ }
@@ -4,17 +4,17 @@ import { Filter } from '@headless-adminapp/core/transport';
4
4
  import { QueryKey } from '@tanstack/react-query';
5
5
  interface UseRetriveRecordProps<S extends SchemaAttributes = SchemaAttributes> {
6
6
  schema: Schema<S>;
7
- search: string;
8
- filter: Filter | null;
9
- sorting: SortingState<S>;
7
+ search?: string;
8
+ filter?: Filter | null;
9
+ sorting?: SortingState<S>;
10
10
  columns: string[];
11
11
  expand?: Partial<Record<string, string[]>>;
12
- maxRecords: number;
12
+ maxRecords?: number;
13
13
  disabled?: boolean;
14
14
  }
15
- export declare function useRetrieveRecordsKey<S extends SchemaAttributes = SchemaAttributes>({ schema, search, filter, sorting, columns, expand, maxRecords, }: UseRetriveRecordProps<S>): (string | number | string[] | Filter | Partial<Record<string, string[]>> | SortingState<S> | null | undefined)[];
15
+ export declare function useRetrieveRecordsKey<S extends SchemaAttributes = SchemaAttributes>({ schema, search, filter, sorting, columns, expand, maxRecords, }: UseRetriveRecordProps<S>): QueryKey;
16
16
  export declare function useClearDataExceptFirstPage(queryKey: QueryKey): void;
17
- export declare function useRetriveRecords<S extends SchemaAttributes = SchemaAttributes>(queryKey: QueryKey, { columns, expand, filter, maxRecords, schema, search, sorting, disabled, }: UseRetriveRecordProps): {
17
+ export declare function useRetriveRecordsInternal<S extends SchemaAttributes = SchemaAttributes>(queryKey: QueryKey, { columns, expand, filter, maxRecords, schema, search, sorting, disabled, }: UseRetriveRecordProps<S>): {
18
18
  data: {
19
19
  logicalName: string;
20
20
  count: number;
@@ -29,5 +29,24 @@ export declare function useRetriveRecords<S extends SchemaAttributes = SchemaAtt
29
29
  data: import("@headless-adminapp/core/transport").RetriveRecordsResult<InferredSchemaType<S>>;
30
30
  }, unknown>, Error>>;
31
31
  isFetchingNextPage: boolean;
32
+ refetch: () => void;
33
+ };
34
+ export declare function useRetriveRecords<S extends SchemaAttributes = SchemaAttributes>({ columns, expand, filter, maxRecords, schema, search, sorting, disabled, }: UseRetriveRecordProps<S>): {
35
+ queryKey: QueryKey;
36
+ data: {
37
+ logicalName: string;
38
+ count: number;
39
+ records: import("@headless-adminapp/core/transport").Data<InferredSchemaType<S>>[];
40
+ } | null;
41
+ isFetching: boolean;
42
+ hasNextPage: boolean;
43
+ fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<{
44
+ params: {
45
+ pageIndex: number;
46
+ };
47
+ data: import("@headless-adminapp/core/transport").RetriveRecordsResult<InferredSchemaType<S>>;
48
+ }, unknown>, Error>>;
49
+ isFetchingNextPage: boolean;
50
+ refetch: () => void;
32
51
  };
33
52
  export {};
@@ -2,11 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useRetrieveRecordsKey = useRetrieveRecordsKey;
4
4
  exports.useClearDataExceptFirstPage = useClearDataExceptFirstPage;
5
+ exports.useRetriveRecordsInternal = useRetriveRecordsInternal;
5
6
  exports.useRetriveRecords = useRetriveRecords;
6
7
  const transport_1 = require("@headless-adminapp/app/transport");
7
8
  const react_query_1 = require("@tanstack/react-query");
8
9
  const react_1 = require("react");
9
10
  const ROWS_PER_PAGE = 100;
11
+ const MAX_RECORDS = 10000;
10
12
  function useRetrieveRecordsKey({ schema, search, filter, sorting, columns, expand, maxRecords, }) {
11
13
  const queryKey = (0, react_1.useMemo)(() => [
12
14
  'data',
@@ -26,31 +28,36 @@ function useClearDataExceptFirstPage(queryKey) {
26
28
  (0, react_1.useEffect)(() => {
27
29
  return () => {
28
30
  // Clear all pages except the first one when the component is unmounted
29
- queryClient.setQueryData(queryKey, (oldData) => {
30
- if (!oldData) {
31
- return oldData;
32
- }
33
- if (!oldData.pageParams.length || !oldData.pages.length) {
34
- return oldData;
35
- }
36
- return {
37
- pageParams: [oldData.pageParams[0]],
38
- pages: [oldData.pages[0]],
39
- };
40
- });
31
+ clearDataExceptFirstPage(queryClient, queryKey);
41
32
  };
42
33
  }, [queryClient, queryKey]);
43
34
  }
44
- function useRetriveRecords(queryKey, { columns, expand, filter, maxRecords, schema, search, sorting, disabled, }) {
35
+ function clearDataExceptFirstPage(queryClient, queryKey) {
36
+ queryClient.setQueryData(queryKey, (oldData) => {
37
+ if (!oldData) {
38
+ return oldData;
39
+ }
40
+ if (!oldData.pageParams.length || !oldData.pages.length) {
41
+ return oldData;
42
+ }
43
+ return {
44
+ pageParams: [oldData.pageParams[0]],
45
+ pages: [oldData.pages[0]],
46
+ };
47
+ });
48
+ }
49
+ function useRetriveRecordsInternal(queryKey, { columns, expand, filter, maxRecords, schema, search, sorting, disabled, }) {
45
50
  const dataService = (0, transport_1.useDataService)();
51
+ const queryClient = (0, react_query_1.useQueryClient)();
46
52
  const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } = (0, react_query_1.useInfiniteQuery)({
47
53
  queryKey: queryKey,
48
54
  queryFn: async (queryContext) => {
49
55
  const params = queryContext.pageParam ?? {
50
56
  pageIndex: 0,
51
57
  };
58
+ const _maxRecords = maxRecords ?? MAX_RECORDS;
52
59
  const skip = params.pageIndex * ROWS_PER_PAGE;
53
- const limit = Math.min(ROWS_PER_PAGE, Math.max(0, maxRecords - skip));
60
+ const limit = Math.min(ROWS_PER_PAGE, Math.max(0, _maxRecords - skip));
54
61
  if (limit <= 0) {
55
62
  return {
56
63
  params: params,
@@ -100,11 +107,45 @@ function useRetriveRecords(queryKey, { columns, expand, filter, maxRecords, sche
100
107
  records: data?.pages.map((x) => x.data.records).flat() ?? [],
101
108
  };
102
109
  }, [data]);
110
+ const refech = (0, react_1.useCallback)(() => {
111
+ clearDataExceptFirstPage(queryClient, queryKey);
112
+ queryClient
113
+ .resetQueries({
114
+ queryKey,
115
+ })
116
+ .catch(console.error);
117
+ }, []);
103
118
  return {
104
119
  data: finalData,
105
120
  isFetching,
106
121
  hasNextPage,
107
122
  fetchNextPage,
108
123
  isFetchingNextPage,
124
+ refetch: refech,
125
+ };
126
+ }
127
+ function useRetriveRecords({ columns, expand, filter, maxRecords, schema, search, sorting, disabled, }) {
128
+ const queryKey = useRetrieveRecordsKey({
129
+ columns,
130
+ expand,
131
+ filter,
132
+ maxRecords,
133
+ schema,
134
+ search,
135
+ sorting,
136
+ });
137
+ const query = useRetriveRecordsInternal(queryKey, {
138
+ columns,
139
+ expand,
140
+ filter,
141
+ maxRecords,
142
+ schema,
143
+ search,
144
+ sorting,
145
+ disabled,
146
+ });
147
+ return {
148
+ ...query,
149
+ queryKey,
109
150
  };
110
151
  }
@@ -3,6 +3,7 @@ interface AttributeFormattedValueStringsSet {
3
3
  yes: string;
4
4
  no: string;
5
5
  }
6
+ export declare function formatDuration(value: number | null): string;
6
7
  export declare function getAttributeFormattedValue<A extends Attribute = Attribute>(attribute: Attribute, value: InferredAttributeType<A> | null | undefined, options?: {
7
8
  maxCount?: number;
8
9
  strings?: AttributeFormattedValueStringsSet;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.formatDuration = formatDuration;
6
7
  exports.getAttributeFormattedValue = getAttributeFormattedValue;
7
8
  const dayjs_1 = __importDefault(require("dayjs"));
8
9
  const timezone_1 = __importDefault(require("dayjs/plugin/timezone"));
@@ -104,11 +105,56 @@ function getAttributeMoneyFormattedValue(value, options) {
104
105
  currencyDisplay,
105
106
  }).format(value);
106
107
  }
108
+ function getAttributeNumberFormattedValue(attribute, value, options) {
109
+ if (attribute.format === 'duration') {
110
+ return formatDuration(value);
111
+ }
112
+ if (attribute.format === 'time') {
113
+ return (0, dayjs_1.default)()
114
+ .startOf('day')
115
+ .add(value, 'minutes')
116
+ .format(options?.timeFormat ?? defaultTimeFormat);
117
+ }
118
+ return new Intl.NumberFormat(options?.locale).format(value);
119
+ }
120
+ function formatDuration(value) {
121
+ if (!value) {
122
+ return '';
123
+ }
124
+ // No decimal, if value is decimal, round to nearest whole number
125
+ // 1 - 1 minute
126
+ // 2-59 minutes -> 2-59 minutes
127
+ // 90 minutes -> 1.5 hours
128
+ // more than a day -> 1 day, 1.5 days, 2 days, etc.
129
+ // check if value has decimal
130
+ if (value % 1 !== 0) {
131
+ // round to nearest whole number
132
+ value = Math.round(value);
133
+ }
134
+ if (!value) {
135
+ return '';
136
+ }
137
+ if (value === 1) {
138
+ return '1 minute';
139
+ }
140
+ if (value < 60) {
141
+ return `${value} minutes`;
142
+ }
143
+ if (value === 60) {
144
+ return '1 hour';
145
+ }
146
+ if (value < 1440) {
147
+ return `${Number((value / 60).toFixed(2))} hours`;
148
+ }
149
+ if (value === 1440) {
150
+ return '1 day';
151
+ }
152
+ return `${Number((value / 1440).toFixed(2))} days`;
153
+ }
107
154
  function getAttributeFormattedValue(attribute, value, options) {
108
155
  if (value === null || value === undefined) {
109
156
  return null;
110
157
  }
111
- const locale = options?.locale ?? defaultLocale;
112
158
  const region = options?.region ?? 'US';
113
159
  switch (attribute.type) {
114
160
  case 'boolean':
@@ -128,7 +174,7 @@ function getAttributeFormattedValue(attribute, value, options) {
128
174
  case 'money':
129
175
  return getAttributeMoneyFormattedValue(value, options);
130
176
  case 'number':
131
- return new Intl.NumberFormat(locale).format(value);
177
+ return getAttributeNumberFormattedValue(attribute, value, options);
132
178
  case 'attachment':
133
179
  return getAttributeAttachmentFormattedValue(value);
134
180
  case 'string':