@xh/hoist 86.2.0 → 86.4.0
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.
- package/.gitattributes +4 -0
- package/CHANGELOG.md +75 -0
- package/admin/AppModel.ts +38 -11
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +56 -6
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +9 -26
- package/admin/tabs/clients/ClientsModel.ts +4 -1
- package/admin/tabs/clients/activity/ClientDetailModel.ts +5 -1
- package/admin/tabs/userData/roles/RoleModel.ts +11 -18
- package/admin/tabs/userData/roles/RolePanel.ts +2 -9
- package/appcontainer/RouterModel.ts +6 -6
- package/build/types/admin/AppModel.d.ts +7 -4
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +14 -3
- package/build/types/admin/tabs/clients/ClientsModel.d.ts +3 -0
- package/build/types/admin/tabs/userData/roles/RoleModel.d.ts +2 -2
- package/build/types/appcontainer/RouterModel.d.ts +5 -5
- package/build/types/cmp/grid/Grid.d.ts +2 -1
- package/build/types/cmp/grid/GridModel.d.ts +2 -2
- package/build/types/cmp/grid/Types.d.ts +8 -1
- package/build/types/cmp/input/SegmentedControlOption.d.ts +12 -0
- package/build/types/core/HoistAppModel.d.ts +2 -3
- package/build/types/core/types/Types.d.ts +12 -0
- package/build/types/data/Store.d.ts +3 -2
- package/build/types/desktop/cmp/filter/FilterChooser.d.ts +5 -0
- package/build/types/desktop/cmp/filter/PopoverFilterChooser.d.ts +3 -1
- package/build/types/desktop/cmp/grid/impl/colchooser/ColChooserModel.d.ts +2 -2
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.d.ts +9 -1
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTabModel.d.ts +8 -2
- package/build/types/desktop/cmp/input/Select.d.ts +8 -0
- package/build/types/kit/react-dropzone/index.d.ts +1 -0
- package/build/types/kit/swiper/index.d.ts +4 -3
- package/build/types/mobile/cmp/grid/impl/ColChooserModel.d.ts +2 -2
- package/build/types/mobile/cmp/input/Select.d.ts +8 -0
- package/build/types/svc/PrefService.d.ts +12 -1
- package/build/types/utils/datetime/LocalDate.d.ts +23 -1
- package/cmp/grid/Grid.ts +10 -2
- package/cmp/grid/GridModel.ts +4 -3
- package/cmp/grid/Types.ts +9 -1
- package/cmp/grid/impl/MenuSupport.ts +1 -1
- package/cmp/input/SegmentedControlOption.ts +14 -0
- package/cmp/store/impl/StoreFilterFieldImplModel.ts +5 -6
- package/core/HoistAppModel.ts +2 -3
- package/core/types/Types.ts +14 -5
- package/data/Store.ts +3 -2
- package/desktop/cmp/filechooser/FileChooser.ts +6 -3
- package/desktop/cmp/filechooser/FileChooserModel.ts +12 -5
- package/desktop/cmp/filter/FilterChooser.scss +42 -0
- package/desktop/cmp/filter/FilterChooser.ts +147 -7
- package/desktop/cmp/filter/PopoverFilterChooser.ts +7 -84
- package/desktop/cmp/grid/editors/impl/InlineEditorModel.ts +6 -1
- package/desktop/cmp/grid/find/impl/GridFindFieldImplModel.ts +3 -5
- package/desktop/cmp/grid/impl/colchooser/ColChooserModel.ts +2 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRow.ts +12 -12
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.ts +18 -0
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTab.scss +5 -0
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTab.ts +13 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomTabModel.ts +97 -14
- package/desktop/cmp/input/SegmentedControl.ts +7 -3
- package/desktop/cmp/input/Select.ts +24 -16
- package/docs/routing.md +4 -2
- package/docs/upgrade-notes/v86-upgrade-notes.md +73 -8
- package/kit/react-dropzone/index.ts +1 -0
- package/mcp/README.md +12 -0
- package/mcp/data/ts-registry.spec.ts +156 -0
- package/mcp/data/ts-registry.ts +13 -6
- package/mcp/formatters/typescript.ts +7 -3
- package/mcp/util/paths.ts +21 -0
- package/mobile/cmp/grid/impl/ColChooserModel.ts +2 -2
- package/mobile/cmp/input/SegmentedControl.ts +7 -3
- package/mobile/cmp/input/Select.ts +21 -15
- package/mobx/README.md +2 -1
- package/package.json +5 -5
- package/svc/PrefService.ts +80 -20
- package/svc/README.md +6 -0
- package/utils/datetime/LocalDate.ts +24 -1
- package/desktop/cmp/filter/PopoverFilterChooser.scss +0 -42
package/.gitattributes
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 86.4.0 - 2026-07-15
|
|
4
|
+
|
|
5
|
+
### 🎁 New Features
|
|
6
|
+
|
|
7
|
+
* Added `PrefService.isSet()` to report whether the current user has an explicit value on file for a
|
|
8
|
+
preference vs. receiving its server-side default - a distinction that cannot be reliably inferred
|
|
9
|
+
by comparing the value to the default. Requires a hoist-core version that emits the backing
|
|
10
|
+
`isSet` flag; against older servers all prefs report as unset.
|
|
11
|
+
|
|
12
|
+
### 🐞 Bug Fixes
|
|
13
|
+
|
|
14
|
+
* `PrefService.unset()` now performs a true server-side unset, clearing the user's stored value so
|
|
15
|
+
the preference reverts to its (possibly changing) default and `isSet()` reports `false`.
|
|
16
|
+
Previously it persisted the current default as an explicit user value. Falls back to the legacy
|
|
17
|
+
behavior against hoist-core versions that predate the `xh/unsetPrefs` endpoint.
|
|
18
|
+
* Fixed `FilterChooser` popover mode to render an opaque background when expanded.
|
|
19
|
+
|
|
20
|
+
## 86.3.0 - 2026-07-10
|
|
21
|
+
|
|
22
|
+
### 🎁 New Features
|
|
23
|
+
|
|
24
|
+
* `Select` now accepts a `generateOptionFn` prop to resolve an option for a selected value that is
|
|
25
|
+
not present in the current options list (e.g. with `queryFn`-based selects or readonly forms),
|
|
26
|
+
ensuring such values render with their proper label rather than falling back to the raw value.
|
|
27
|
+
* `SegmentedControl` options (desktop and mobile) now accept a `testId`, emitted on the option's
|
|
28
|
+
rendered button as `data-testid` for E2E targeting. If an option omits its own `testId` but the
|
|
29
|
+
control has one, an id is auto-derived as `${controlTestId}-${value}` - restoring parity with
|
|
30
|
+
the legacy `ButtonGroupInput` test-hook pattern for apps migrating between the two.
|
|
31
|
+
|
|
32
|
+
### 🐞 Bug Fixes
|
|
33
|
+
|
|
34
|
+
* Fixed grid columns configured as `hidden` becoming visible after being grouped and then
|
|
35
|
+
ungrouped. `GridModel` now re-asserts each column's configured visibility whenever `groupBy`
|
|
36
|
+
changes, keeping AG Grid's column state in sync with `columnState`.
|
|
37
|
+
* Fixed `StoreFilterField` and grid Find so an active quick-filter or find query no longer returns
|
|
38
|
+
different results when the grid's `groupBy` changes.
|
|
39
|
+
* Fixed inline grid cell editors to reliably commit their value when editing ends, including popup
|
|
40
|
+
editors (e.g. `textAreaEditor`) within a dialog, which previously dropped edits on Enter or
|
|
41
|
+
click-away.
|
|
42
|
+
* Fixed `Select` to correctly handle non-primitive (object) values: selected-option matching and
|
|
43
|
+
async query de-duplication now use deep equality, so object values no longer render as
|
|
44
|
+
`[object Object]` or collide with one another.
|
|
45
|
+
* Hardened the grid column filter's Custom tab against filters it previously mishandled -
|
|
46
|
+
multi-value clauses are now expanded into editable rows and recombined on commit, and filters it
|
|
47
|
+
cannot represent are left untouched rather than corrupted.
|
|
48
|
+
* Fixed `FilterChooser` popover mode (formerly `PopoverFilterChooser`) so its collapsed control no
|
|
49
|
+
longer disappears when opened - it now always occupies its place in the layout, so surrounding
|
|
50
|
+
elements no longer shift. Its clear and favorites controls also respond to a single click rather
|
|
51
|
+
than requiring the popover to be opened first. This mode is now enabled more naturally via
|
|
52
|
+
a new option `filterChooser({popover: true})`, deprecating `PopoverFilterChooser`, which remains
|
|
53
|
+
as a thin alias.
|
|
54
|
+
* Fixed "not a valid MIME type" console warnings from `FileChooser`. Accepted extensions are now
|
|
55
|
+
passed under a dummy MIME type key, silencing the warnings while continuing to filter selected
|
|
56
|
+
files by extension.
|
|
57
|
+
|
|
58
|
+
### ⚙️ Typescript API Adjustments
|
|
59
|
+
|
|
60
|
+
* Retyped `GridModel.colChooserModel` as the new cross-platform `IColChooserModel` interface,
|
|
61
|
+
replacing the bare `HoistModel` type and exposing `isOpen`, `open()`, and `close()` directly.
|
|
62
|
+
* Added the exported `HoistRoute` type - Router5's `Route` extended with Hoist's `omit` key - and
|
|
63
|
+
retyped `HoistAppModel.getRoutes()` to return it, so declarative route exclusion (e.g.
|
|
64
|
+
`omit: !XH.getUser().isHoistAdmin`) now type-checks without a cast.
|
|
65
|
+
|
|
66
|
+
### 🤖 AI Docs + Tooling
|
|
67
|
+
|
|
68
|
+
* Fixed the MCP server and `hoist-ts` CLI TypeScript symbol tools (`search`, `symbol`, `members`)
|
|
69
|
+
returning no results on Windows, where a path-separator mismatch left the symbol index empty.
|
|
70
|
+
Path handling is now normalized so the developer tools work on Windows as well as macOS/Linux.
|
|
71
|
+
|
|
72
|
+
### 📚 Libraries
|
|
73
|
+
|
|
74
|
+
* @auth0/auth0-spa-js `2.21 → 2.23`
|
|
75
|
+
* @azure/msal-browser `5.14 → 5.16`
|
|
76
|
+
* swiper `12.1 -> 14.0`
|
|
77
|
+
|
|
3
78
|
## 86.2.0 - 2026-06-25
|
|
4
79
|
|
|
5
80
|
### 🎁 New Features
|
package/admin/AppModel.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
8
8
|
import {TabConfig, TabContainerModel} from '@xh/hoist/cmp/tab';
|
|
9
9
|
import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
|
|
10
|
-
import {HoistAppModel, InitContext, XH} from '@xh/hoist/core';
|
|
10
|
+
import {HoistAppModel, HoistRoute, InitContext, XH} from '@xh/hoist/core';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
12
|
+
import {SECONDS} from '@xh/hoist/utils/datetime';
|
|
12
13
|
import {without} from 'lodash';
|
|
13
|
-
import {
|
|
14
|
+
import {RoleModuleConfig} from './tabs/userData/roles/Types';
|
|
14
15
|
import {activityTrackingPanel} from './tabs/activity/tracking/ActivityTrackingPanel';
|
|
15
16
|
import {clientsPanel} from './tabs/clients/ClientsPanel';
|
|
16
17
|
import {monitorTab} from './tabs/monitor/MonitorTab';
|
|
@@ -28,6 +29,9 @@ export class AppModel extends HoistAppModel {
|
|
|
28
29
|
|
|
29
30
|
viewManagerModels: Record<string, ViewManagerModel> = {};
|
|
30
31
|
|
|
32
|
+
/** Role-module config, loaded once at init and shared with the Roles tab. */
|
|
33
|
+
roleModuleConfig: RoleModuleConfig = null;
|
|
34
|
+
|
|
31
35
|
static get readonly() {
|
|
32
36
|
return !XH.getUser().isHoistAdmin;
|
|
33
37
|
}
|
|
@@ -35,21 +39,23 @@ export class AppModel extends HoistAppModel {
|
|
|
35
39
|
constructor() {
|
|
36
40
|
super();
|
|
37
41
|
|
|
38
|
-
this.tabModel = new TabContainerModel({
|
|
39
|
-
route: 'default',
|
|
40
|
-
tabs: this.createTabs()
|
|
41
|
-
});
|
|
42
|
-
|
|
43
42
|
// Enable managed autosize mode across Hoist Admin console grids.
|
|
44
43
|
GridModel.defaults.autosizeMode = 'managed';
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
override async initAsync(ctx: InitContext) {
|
|
47
|
+
await this.loadRoleModuleConfigAsync(ctx);
|
|
48
|
+
|
|
49
|
+
this.tabModel = new TabContainerModel({
|
|
50
|
+
route: 'default',
|
|
51
|
+
tabs: this.createTabs()
|
|
52
|
+
});
|
|
53
|
+
|
|
48
54
|
await this.initViewManagerModelsAsync(ctx);
|
|
49
55
|
await super.initAsync(ctx);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
override getRoutes():
|
|
58
|
+
override getRoutes(): HoistRoute[] {
|
|
53
59
|
return [
|
|
54
60
|
{
|
|
55
61
|
name: 'default',
|
|
@@ -66,7 +72,7 @@ export class AppModel extends HoistAppModel {
|
|
|
66
72
|
return [];
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
getTabRoutes():
|
|
75
|
+
getTabRoutes(): HoistRoute[] {
|
|
70
76
|
return [
|
|
71
77
|
{
|
|
72
78
|
name: 'general',
|
|
@@ -123,7 +129,8 @@ export class AppModel extends HoistAppModel {
|
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
createTabs(): TabConfig[] {
|
|
126
|
-
const conf = XH.getConf('xhAdminAppConfig', {})
|
|
132
|
+
const conf = XH.getConf('xhAdminAppConfig', {}),
|
|
133
|
+
rolesEnabled = this.roleModuleConfig?.enabled ?? false;
|
|
127
134
|
|
|
128
135
|
return [
|
|
129
136
|
{
|
|
@@ -160,6 +167,7 @@ export class AppModel extends HoistAppModel {
|
|
|
160
167
|
},
|
|
161
168
|
{
|
|
162
169
|
id: 'userData',
|
|
170
|
+
title: rolesEnabled ? 'User Data & Roles' : 'User Data',
|
|
163
171
|
icon: Icon.users(),
|
|
164
172
|
content: {
|
|
165
173
|
refreshMode: 'onShowAlways',
|
|
@@ -173,7 +181,8 @@ export class AppModel extends HoistAppModel {
|
|
|
173
181
|
{
|
|
174
182
|
id: 'roles',
|
|
175
183
|
icon: Icon.idBadge(),
|
|
176
|
-
content: rolePanel
|
|
184
|
+
content: rolePanel,
|
|
185
|
+
omit: !rolesEnabled
|
|
177
186
|
},
|
|
178
187
|
{
|
|
179
188
|
id: 'prefs',
|
|
@@ -220,4 +229,22 @@ export class AppModel extends HoistAppModel {
|
|
|
220
229
|
ctx
|
|
221
230
|
);
|
|
222
231
|
}
|
|
232
|
+
|
|
233
|
+
//----------------
|
|
234
|
+
// Implementation
|
|
235
|
+
//----------------
|
|
236
|
+
private async loadRoleModuleConfigAsync(ctx: InitContext) {
|
|
237
|
+
// Load role config up-front to title/show the Roles tab (see createTabs).
|
|
238
|
+
// Never block startup if it can't load - the tab defaults to hidden.
|
|
239
|
+
try {
|
|
240
|
+
this.roleModuleConfig = await this.runner(ctx)
|
|
241
|
+
.span('loadRoleModuleConfig')
|
|
242
|
+
.fetchJson({url: 'roleAdmin/config', timeout: 10 * SECONDS});
|
|
243
|
+
} catch (e) {
|
|
244
|
+
XH.handleException(e, {
|
|
245
|
+
message: 'Unable to load roles configuration',
|
|
246
|
+
alertType: 'toast'
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
223
250
|
}
|
|
@@ -15,7 +15,17 @@ import {FilterChooserModel} from '@xh/hoist/cmp/filter';
|
|
|
15
15
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
16
16
|
import {ColumnRenderer, ColumnSpec, GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
|
|
17
17
|
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
HoistModel,
|
|
20
|
+
LoadSpec,
|
|
21
|
+
LocalDateUnit,
|
|
22
|
+
managed,
|
|
23
|
+
PersistableState,
|
|
24
|
+
PersistenceProvider,
|
|
25
|
+
persistOptions,
|
|
26
|
+
PlainObject,
|
|
27
|
+
XH
|
|
28
|
+
} from '@xh/hoist/core';
|
|
19
29
|
import {Cube, CubeFieldSpec, FieldSpec, ViewRowData} from '@xh/hoist/data';
|
|
20
30
|
import {dateRenderer, dateTimeSecRenderer, numberRenderer} from '@xh/hoist/format';
|
|
21
31
|
import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
@@ -24,6 +34,23 @@ import {compact, get, isEmpty, isEqual, round} from 'lodash';
|
|
|
24
34
|
import moment from 'moment';
|
|
25
35
|
import {ActivityDetailProvider} from './detail/ActivityDetailModel';
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A relative time interval (e.g. 7 days) ending on the current app day. Drives the quick-select
|
|
39
|
+
* buttons and the persisted time period, which rolls forward to end on today on restore.
|
|
40
|
+
*/
|
|
41
|
+
export interface ActivityInterval {
|
|
42
|
+
value: number;
|
|
43
|
+
unit: LocalDateUnit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Quick-select intervals offered in the toolbar - labels are derived as `${value}${unit[0]}`. */
|
|
47
|
+
export const INTERVALS: ActivityInterval[] = [
|
|
48
|
+
{value: 6, unit: 'months'},
|
|
49
|
+
{value: 1, unit: 'months'},
|
|
50
|
+
{value: 7, unit: 'days'},
|
|
51
|
+
{value: 1, unit: 'days'}
|
|
52
|
+
];
|
|
53
|
+
|
|
27
54
|
export class ActivityTrackingModel extends HoistModel implements ActivityDetailProvider {
|
|
28
55
|
override telemetryPrefix = 'xh.client.admin.tracking';
|
|
29
56
|
|
|
@@ -115,6 +142,7 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
|
|
|
115
142
|
this.markPersist('showFilterChooser');
|
|
116
143
|
|
|
117
144
|
this.formModel = this.createQueryFormModel();
|
|
145
|
+
this.setupPeriodPersistence();
|
|
118
146
|
|
|
119
147
|
this.dataFieldsEditorModel = new DataFieldsEditorModel(this);
|
|
120
148
|
this.markPersist('dataFields');
|
|
@@ -203,17 +231,17 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
|
|
|
203
231
|
endDay.setValue(newEnd);
|
|
204
232
|
}
|
|
205
233
|
|
|
206
|
-
// Set the start date by taking the end date and pushing back
|
|
234
|
+
// Set the start date by taking the end date and pushing back the interval - then pushing
|
|
207
235
|
// forward one day as the day range query is inclusive.
|
|
208
|
-
adjustStartDate(
|
|
236
|
+
adjustStartDate(interval: ActivityInterval) {
|
|
209
237
|
this.formModel.setValues({
|
|
210
|
-
startDay: this.endDay.subtract(value, unit).nextDay()
|
|
238
|
+
startDay: this.endDay.subtract(interval.value, interval.unit).nextDay()
|
|
211
239
|
});
|
|
212
240
|
}
|
|
213
241
|
|
|
214
|
-
isInterval(
|
|
242
|
+
isInterval(interval: ActivityInterval) {
|
|
215
243
|
const {startDay, endDay} = this.formModel.values;
|
|
216
|
-
return startDay === endDay.subtract(value, unit).nextDay();
|
|
244
|
+
return startDay === endDay.subtract(interval.value, interval.unit).nextDay();
|
|
217
245
|
}
|
|
218
246
|
|
|
219
247
|
getDisplayName(fieldName: string) {
|
|
@@ -234,6 +262,28 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
|
|
|
234
262
|
});
|
|
235
263
|
}
|
|
236
264
|
|
|
265
|
+
// Persist the selected time period as part of the active view, if endDay is currentDay
|
|
266
|
+
private setupPeriodPersistence() {
|
|
267
|
+
PersistenceProvider.create<ActivityInterval>({
|
|
268
|
+
persistOptions: persistOptions({path: 'queryPeriod'}, this.persistWith),
|
|
269
|
+
owner: this,
|
|
270
|
+
target: {
|
|
271
|
+
getPersistableState: () => {
|
|
272
|
+
const currDay = this.endDay === LocalDate.currentAppDay(),
|
|
273
|
+
interval = currDay ? INTERVALS.find(it => this.isInterval(it)) : null;
|
|
274
|
+
return new PersistableState(interval ?? null);
|
|
275
|
+
},
|
|
276
|
+
setPersistableState: ({value}) => {
|
|
277
|
+
if (value) {
|
|
278
|
+
const endDay = LocalDate.currentAppDay();
|
|
279
|
+
this.formModel.setValues({endDay});
|
|
280
|
+
this.adjustStartDate(value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
237
287
|
private async loadGridAsync() {
|
|
238
288
|
const {cube, gridModel, dimensions} = this,
|
|
239
289
|
data = cube.executeQuery({
|
|
@@ -11,7 +11,7 @@ import {grid} from '@xh/hoist/cmp/grid';
|
|
|
11
11
|
import {div, filler, hframe} from '@xh/hoist/cmp/layout';
|
|
12
12
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
13
13
|
import {button, buttonGroup, colChooserButton, exportButton} from '@xh/hoist/desktop/cmp/button';
|
|
14
|
-
import {
|
|
14
|
+
import {filterChooser} from '@xh/hoist/desktop/cmp/filter';
|
|
15
15
|
import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
16
16
|
import {groupingChooser} from '@xh/hoist/desktop/cmp/grouping';
|
|
17
17
|
import {dateInput, DateInputProps, select} from '@xh/hoist/desktop/cmp/input';
|
|
@@ -20,7 +20,7 @@ import {toolbar, toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
|
|
|
20
20
|
import {viewManager} from '@xh/hoist/desktop/cmp/viewmanager';
|
|
21
21
|
import {Icon} from '@xh/hoist/icon';
|
|
22
22
|
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
23
|
-
import {ActivityTrackingModel} from './ActivityTrackingModel';
|
|
23
|
+
import {INTERVALS, ActivityTrackingModel} from './ActivityTrackingModel';
|
|
24
24
|
import {aggChartPanel} from '@xh/hoist/admin/tabs/activity/tracking/chart/AggChartPanel';
|
|
25
25
|
import {activityDetailView} from './detail/ActivityDetailView';
|
|
26
26
|
import './ActivityTracking.scss';
|
|
@@ -75,32 +75,14 @@ const tbar = hoistCmp.factory<ActivityTrackingModel>(({model}) => {
|
|
|
75
75
|
disabled: model.endDay >= LocalDate.currentAppDay()
|
|
76
76
|
}),
|
|
77
77
|
buttonGroup({
|
|
78
|
-
items:
|
|
78
|
+
items: INTERVALS.map(interval =>
|
|
79
79
|
button({
|
|
80
|
-
text:
|
|
81
|
-
onClick: () => model.adjustStartDate(
|
|
82
|
-
active: model.isInterval(
|
|
83
|
-
...dateBtn
|
|
84
|
-
}),
|
|
85
|
-
button({
|
|
86
|
-
text: '1m',
|
|
87
|
-
onClick: () => model.adjustStartDate(1, 'months'),
|
|
88
|
-
active: model.isInterval(1, 'months'),
|
|
89
|
-
...dateBtn
|
|
90
|
-
}),
|
|
91
|
-
button({
|
|
92
|
-
text: '7d',
|
|
93
|
-
onClick: () => model.adjustStartDate(7, 'days'),
|
|
94
|
-
active: model.isInterval(7, 'days'),
|
|
95
|
-
...dateBtn
|
|
96
|
-
}),
|
|
97
|
-
button({
|
|
98
|
-
text: '1d',
|
|
99
|
-
onClick: () => model.adjustStartDate(1, 'days'),
|
|
100
|
-
active: model.isInterval(1, 'days'),
|
|
80
|
+
text: `${interval.value}${interval.unit[0]}`,
|
|
81
|
+
onClick: () => model.adjustStartDate(interval),
|
|
82
|
+
active: model.isInterval(interval),
|
|
101
83
|
...dateBtn
|
|
102
84
|
})
|
|
103
|
-
|
|
85
|
+
)
|
|
104
86
|
}),
|
|
105
87
|
toolbarSep(),
|
|
106
88
|
filterChooserToggleButton(),
|
|
@@ -137,7 +119,8 @@ const filterChooserToggleButton = hoistCmp.factory<ActivityTrackingModel>(({mode
|
|
|
137
119
|
const filterBar = hoistCmp.factory<ActivityTrackingModel>(({model}) => {
|
|
138
120
|
return model.showFilterChooser
|
|
139
121
|
? toolbar(
|
|
140
|
-
|
|
122
|
+
filterChooser({
|
|
123
|
+
popover: true,
|
|
141
124
|
flex: 1,
|
|
142
125
|
enableClear: true
|
|
143
126
|
})
|
|
@@ -9,7 +9,7 @@ import {AppModel} from '@xh/hoist/admin/AppModel';
|
|
|
9
9
|
import * as Col from '@xh/hoist/admin/columns';
|
|
10
10
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
11
11
|
import {div, p} from '@xh/hoist/cmp/layout';
|
|
12
|
-
import {LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
12
|
+
import {LoadSpec, managed, persistOptions, XH} from '@xh/hoist/core';
|
|
13
13
|
import {RecordActionSpec, StoreRecord} from '@xh/hoist/data';
|
|
14
14
|
import {textInput} from '@xh/hoist/desktop/cmp/input';
|
|
15
15
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -23,6 +23,8 @@ import {BaseAdminTabModel} from '@xh/hoist/admin/tabs/BaseAdminTabModel';
|
|
|
23
23
|
export class ClientsModel extends BaseAdminTabModel {
|
|
24
24
|
override telemetryPrefix = 'xh.client.admin.clients';
|
|
25
25
|
|
|
26
|
+
override persistWith = {localStorageKey: 'xhAdminClientsState'};
|
|
27
|
+
|
|
26
28
|
@observable
|
|
27
29
|
lastRefresh: number;
|
|
28
30
|
|
|
@@ -150,6 +152,7 @@ export class ClientsModel extends BaseAdminTabModel {
|
|
|
150
152
|
return new GridModel({
|
|
151
153
|
emptyText: 'No clients connected.',
|
|
152
154
|
groupBy: this.groupBy,
|
|
155
|
+
persistWith: persistOptions(this.persistWith, {path: 'mainGrid'}),
|
|
153
156
|
colChooserModel: true,
|
|
154
157
|
enableExport: true,
|
|
155
158
|
selModel: 'multiple',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {ClientsModel} from '../ClientsModel';
|
|
8
8
|
import {ColumnSpec} from '@xh/hoist/cmp/grid';
|
|
9
|
-
import {HoistModel, LoadSpec, lookup, PlainObject, XH} from '@xh/hoist/core';
|
|
9
|
+
import {HoistModel, LoadSpec, lookup, persistOptions, PlainObject, XH} from '@xh/hoist/core';
|
|
10
10
|
import {StoreRecord} from '@xh/hoist/data';
|
|
11
11
|
import {bindable, computed, makeObservable} from '@xh/hoist/mobx';
|
|
12
12
|
import {ReactNode} from 'react';
|
|
@@ -53,6 +53,10 @@ export class ClientDetailModel extends HoistModel implements ActivityDetailProvi
|
|
|
53
53
|
override onLinked() {
|
|
54
54
|
super.onLinked();
|
|
55
55
|
|
|
56
|
+
this.persistWith = persistOptions(this.clientsModel.persistWith, {
|
|
57
|
+
pathPrefix: 'activityGrid'
|
|
58
|
+
});
|
|
59
|
+
|
|
56
60
|
this.addReaction(
|
|
57
61
|
{
|
|
58
62
|
track: () => this.clientsModel.gridModel.selectedRecord,
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {getAppModel} from '@xh/hoist/admin/AdminUtils';
|
|
7
8
|
import {RecategorizeDialogModel} from '@xh/hoist/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel';
|
|
8
9
|
import {FilterChooserModel} from '@xh/hoist/cmp/filter';
|
|
9
10
|
import {GridModel, tagsRenderer, TreeStyle} from '@xh/hoist/cmp/grid';
|
|
10
11
|
import * as Col from '@xh/hoist/cmp/grid/columns';
|
|
11
12
|
import {fragment, p} from '@xh/hoist/cmp/layout';
|
|
12
|
-
import {
|
|
13
|
+
import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
13
14
|
import {RecordActionSpec} from '@xh/hoist/data';
|
|
14
15
|
import {actionCol, calcActionColWidth} from '@xh/hoist/desktop/cmp/grid';
|
|
15
16
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -39,10 +40,14 @@ export class RoleModel extends HoistModel {
|
|
|
39
40
|
@managed recategorizeDialogModel = new RecategorizeDialogModel(this);
|
|
40
41
|
|
|
41
42
|
@observable.ref allRoles: HoistRole[] = [];
|
|
42
|
-
@observable.ref moduleConfig: RoleModuleConfig;
|
|
43
43
|
|
|
44
44
|
@bindable showInGroups = true;
|
|
45
45
|
|
|
46
|
+
/** Role-module config - loaded at init. */
|
|
47
|
+
get moduleConfig(): RoleModuleConfig {
|
|
48
|
+
return getAppModel().roleModuleConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
get readonly() {
|
|
47
52
|
return !XH.getUser().isHoistRoleManager;
|
|
48
53
|
}
|
|
@@ -56,6 +61,10 @@ export class RoleModel extends HoistModel {
|
|
|
56
61
|
constructor() {
|
|
57
62
|
super();
|
|
58
63
|
makeObservable(this);
|
|
64
|
+
|
|
65
|
+
this.gridModel = this.createGridModel();
|
|
66
|
+
this.filterChooserModel = this.createFilterChooserModel();
|
|
67
|
+
|
|
59
68
|
this.addReaction({
|
|
60
69
|
track: () => this.showInGroups,
|
|
61
70
|
run: showInGroups => {
|
|
@@ -74,9 +83,6 @@ export class RoleModel extends HoistModel {
|
|
|
74
83
|
return this.runner({loadSpec})
|
|
75
84
|
.span('list')
|
|
76
85
|
.run(async ctx => {
|
|
77
|
-
await this.ensureInitializedAsync(ctx);
|
|
78
|
-
if (!this.moduleConfig.enabled) return;
|
|
79
|
-
|
|
80
86
|
const {data} = await XH.fetchJson({url: 'roleAdmin/list'}, ctx);
|
|
81
87
|
if (loadSpec.isStale) return;
|
|
82
88
|
|
|
@@ -233,19 +239,6 @@ export class RoleModel extends HoistModel {
|
|
|
233
239
|
gridModel.autosizeAsync({includeCollapsedChildren: true});
|
|
234
240
|
}
|
|
235
241
|
|
|
236
|
-
private async ensureInitializedAsync(ctx: CallContext) {
|
|
237
|
-
if (this.moduleConfig) return;
|
|
238
|
-
|
|
239
|
-
const config = await this.runner(ctx).fetchJson({url: 'roleAdmin/config'});
|
|
240
|
-
runInAction(() => {
|
|
241
|
-
this.moduleConfig = config;
|
|
242
|
-
if (config.enabled) {
|
|
243
|
-
this.gridModel = this.createGridModel();
|
|
244
|
-
this.filterChooserModel = this.createFilterChooserModel();
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
242
|
private processRolesFromServer(roles: Partial<HoistRole>[]): HoistRole[] {
|
|
250
243
|
return roles.map(role => {
|
|
251
244
|
const membersByType = mapValues(groupBy(role.members, 'type'), members =>
|
|
@@ -9,8 +9,7 @@ import {grid} from '@xh/hoist/cmp/grid';
|
|
|
9
9
|
import {fragment, hframe, vframe} from '@xh/hoist/cmp/layout';
|
|
10
10
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
11
11
|
import {button, colChooserButton} from '@xh/hoist/desktop/cmp/button';
|
|
12
|
-
import {
|
|
13
|
-
import {popoverFilterChooser} from '@xh/hoist/desktop/cmp/filter';
|
|
12
|
+
import {filterChooser} from '@xh/hoist/desktop/cmp/filter';
|
|
14
13
|
import {switchInput} from '@xh/hoist/desktop/cmp/input';
|
|
15
14
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
16
15
|
import {recordActionBar} from '@xh/hoist/desktop/cmp/record';
|
|
@@ -25,12 +24,6 @@ export const rolePanel = hoistCmp.factory({
|
|
|
25
24
|
model: creates(RoleModel),
|
|
26
25
|
|
|
27
26
|
render({className, model}) {
|
|
28
|
-
const {moduleConfig} = model;
|
|
29
|
-
if (!moduleConfig) return null;
|
|
30
|
-
if (!moduleConfig.enabled) {
|
|
31
|
-
return errorMessage({error: 'Default Role Module not enabled.'});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
27
|
const {gridModel, readonly} = model;
|
|
35
28
|
return fragment(
|
|
36
29
|
panel({
|
|
@@ -44,7 +37,7 @@ export const rolePanel = hoistCmp.factory({
|
|
|
44
37
|
selModel: gridModel.selModel
|
|
45
38
|
}),
|
|
46
39
|
'-',
|
|
47
|
-
|
|
40
|
+
filterChooser({popover: true, flex: 1}),
|
|
48
41
|
'-',
|
|
49
42
|
switchInput({
|
|
50
43
|
bind: 'showInGroups',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {HoistModel} from '../core';
|
|
7
|
+
import {HoistModel, HoistRoute} from '../core';
|
|
8
8
|
import {action, observable, makeObservable} from '@xh/hoist/mobx';
|
|
9
9
|
import {mergeDeep} from '@xh/hoist/utils/js';
|
|
10
10
|
import {isOmitted} from '@xh/hoist/utils/impl';
|
|
@@ -38,11 +38,11 @@ export class RouterModel extends HoistModel {
|
|
|
38
38
|
/**
|
|
39
39
|
* Add routes to the router.
|
|
40
40
|
*
|
|
41
|
-
* @param routes - collection of
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* @param routes - collection of {@link HoistRoute} specs. In addition to the standard Router5
|
|
42
|
+
* route config, each spec supports an `omit` keyword to allow declarative exclusion.
|
|
43
|
+
* Otherwise these are Router5 configs to be passed directly to the Router5 API.
|
|
44
44
|
*/
|
|
45
|
-
addRoutes(routes:
|
|
45
|
+
addRoutes(routes: HoistRoute[]) {
|
|
46
46
|
this.router.add(this.preprocessRoutes(routes));
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -100,7 +100,7 @@ export class RouterModel extends HoistModel {
|
|
|
100
100
|
return ret;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
private preprocessRoutes(routes) {
|
|
103
|
+
private preprocessRoutes(routes: HoistRoute[]): HoistRoute[] {
|
|
104
104
|
const ret = routes.filter(r => !isOmitted(r));
|
|
105
105
|
ret.forEach(r => {
|
|
106
106
|
if (r.children) r.children = this.preprocessRoutes(r.children);
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { TabConfig, TabContainerModel } from '@xh/hoist/cmp/tab';
|
|
2
2
|
import { ViewManagerModel } from '@xh/hoist/cmp/viewmanager';
|
|
3
|
-
import { HoistAppModel, InitContext } from '@xh/hoist/core';
|
|
4
|
-
import {
|
|
3
|
+
import { HoistAppModel, HoistRoute, InitContext } from '@xh/hoist/core';
|
|
4
|
+
import { RoleModuleConfig } from './tabs/userData/roles/Types';
|
|
5
5
|
export declare class AppModel extends HoistAppModel {
|
|
6
6
|
tabModel: TabContainerModel;
|
|
7
7
|
viewManagerModels: Record<string, ViewManagerModel>;
|
|
8
|
+
/** Role-module config, loaded once at init and shared with the Roles tab. */
|
|
9
|
+
roleModuleConfig: RoleModuleConfig;
|
|
8
10
|
static get readonly(): boolean;
|
|
9
11
|
constructor();
|
|
10
12
|
initAsync(ctx: InitContext): Promise<void>;
|
|
11
|
-
getRoutes():
|
|
13
|
+
getRoutes(): HoistRoute[];
|
|
12
14
|
getAppMenuButtonExtraItems(): any[];
|
|
13
|
-
getTabRoutes():
|
|
15
|
+
getTabRoutes(): HoistRoute[];
|
|
14
16
|
createTabs(): TabConfig[];
|
|
15
17
|
/** Open the primary business-facing application, typically 'app'. */
|
|
16
18
|
openPrimaryApp(): void;
|
|
17
19
|
getPrimaryAppCode(): string;
|
|
18
20
|
initViewManagerModelsAsync(ctx: InitContext): Promise<void>;
|
|
21
|
+
private loadRoleModuleConfigAsync;
|
|
19
22
|
}
|
|
@@ -3,10 +3,20 @@ import { FilterChooserModel } from '@xh/hoist/cmp/filter';
|
|
|
3
3
|
import { FormModel } from '@xh/hoist/cmp/form';
|
|
4
4
|
import { ColumnSpec, GridModel } from '@xh/hoist/cmp/grid';
|
|
5
5
|
import { GroupingChooserModel } from '@xh/hoist/cmp/grouping';
|
|
6
|
-
import { HoistModel, LoadSpec, PlainObject } from '@xh/hoist/core';
|
|
6
|
+
import { HoistModel, LoadSpec, LocalDateUnit, PlainObject } from '@xh/hoist/core';
|
|
7
7
|
import { Cube } from '@xh/hoist/data';
|
|
8
8
|
import { LocalDate } from '@xh/hoist/utils/datetime';
|
|
9
9
|
import { ActivityDetailProvider } from './detail/ActivityDetailModel';
|
|
10
|
+
/**
|
|
11
|
+
* A relative time interval (e.g. 7 days) ending on the current app day. Drives the quick-select
|
|
12
|
+
* buttons and the persisted time period, which rolls forward to end on today on restore.
|
|
13
|
+
*/
|
|
14
|
+
export interface ActivityInterval {
|
|
15
|
+
value: number;
|
|
16
|
+
unit: LocalDateUnit;
|
|
17
|
+
}
|
|
18
|
+
/** Quick-select intervals offered in the toolbar - labels are derived as `${value}${unit[0]}`. */
|
|
19
|
+
export declare const INTERVALS: ActivityInterval[];
|
|
10
20
|
export declare class ActivityTrackingModel extends HoistModel implements ActivityDetailProvider {
|
|
11
21
|
telemetryPrefix: string;
|
|
12
22
|
/** FormModel for server-side querying controls. */
|
|
@@ -45,10 +55,11 @@ export declare class ActivityTrackingModel extends HoistModel implements Activit
|
|
|
45
55
|
setDataFields(dataFields: ActivityTrackingDataFieldSpec[]): void;
|
|
46
56
|
toggleFilterChooser(): void;
|
|
47
57
|
adjustDates(dir: 'add' | 'subtract'): void;
|
|
48
|
-
adjustStartDate(
|
|
49
|
-
isInterval(
|
|
58
|
+
adjustStartDate(interval: ActivityInterval): void;
|
|
59
|
+
isInterval(interval: ActivityInterval): boolean;
|
|
50
60
|
getDisplayName(fieldName: string): string;
|
|
51
61
|
private createQueryFormModel;
|
|
62
|
+
private setupPeriodPersistence;
|
|
52
63
|
private loadGridAsync;
|
|
53
64
|
private cubeLabelComparator;
|
|
54
65
|
private getComparableValForDim;
|
|
@@ -4,6 +4,9 @@ import { RecordActionSpec, StoreRecord } from '@xh/hoist/data';
|
|
|
4
4
|
import { BaseAdminTabModel } from '@xh/hoist/admin/tabs/BaseAdminTabModel';
|
|
5
5
|
export declare class ClientsModel extends BaseAdminTabModel {
|
|
6
6
|
telemetryPrefix: string;
|
|
7
|
+
persistWith: {
|
|
8
|
+
localStorageKey: string;
|
|
9
|
+
};
|
|
7
10
|
lastRefresh: number;
|
|
8
11
|
gridModel: GridModel;
|
|
9
12
|
groupBy: 'user' | 'instance';
|
|
@@ -19,8 +19,9 @@ export declare class RoleModel extends HoistModel {
|
|
|
19
19
|
readonly roleEditorModel: RoleEditorModel;
|
|
20
20
|
recategorizeDialogModel: RecategorizeDialogModel;
|
|
21
21
|
allRoles: HoistRole[];
|
|
22
|
-
moduleConfig: RoleModuleConfig;
|
|
23
22
|
showInGroups: boolean;
|
|
23
|
+
/** Role-module config - loaded at init. */
|
|
24
|
+
get moduleConfig(): RoleModuleConfig;
|
|
24
25
|
get readonly(): boolean;
|
|
25
26
|
get selectedRole(): HoistRole;
|
|
26
27
|
constructor();
|
|
@@ -36,7 +37,6 @@ export declare class RoleModel extends HoistModel {
|
|
|
36
37
|
private deleteAction;
|
|
37
38
|
private groupByAction;
|
|
38
39
|
private displayRoles;
|
|
39
|
-
private ensureInitializedAsync;
|
|
40
40
|
private processRolesFromServer;
|
|
41
41
|
private processRolesForTreeGrid;
|
|
42
42
|
private createGridModel;
|