cabloy 5.1.162 → 5.1.164
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/.cabloy-version +1 -1
- package/CHANGELOG.md +16 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/repo-docs/backend/shared-rbac-architecture.md +4 -0
- package/repo-docs/blogs/ai-spec-driven-development/cover-en-v1.png +0 -0
- package/repo-docs/blogs/ai-spec-driven-development/implementation-burndown.png +0 -0
- package/repo-docs/blogs/ai-spec-driven-development/implementation-gantt.png +0 -0
- package/repo-docs/blogs/ai-spec-driven-development/index.md +217 -0
- package/repo-docs/blogs/index.md +15 -0
- package/repo-docs/editions/overview.md +2 -0
- package/repo-docs/frontend/environment-config-guide.md +43 -10
- package/repo-docs/frontend/resource-list-page-deep-dive.md +14 -1
- package/repo-docs/frontend/table-action-visibility-permission-flow-guide.md +20 -0
- package/repo-docs/frontend/table-guide.md +42 -0
- package/repo-docs/frontend/table-resource-crud-cookbook.md +25 -1
- package/repo-e2e/docs/specs/blogs-index.spec.ts +45 -9
- package/repo-e2e/specs/cabloy-basic.spec.ts +249 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +17 -6
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}DeleteBulk.tsx_ +14 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}SelectResItem.tsx_ +7 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/service/{{resourceName}}.ts_ +6 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +12 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +12 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +8 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}DeleteBulk.tsx_ +14 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}SelectResItem.tsx_ +7 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/service/{{resourceName}}.ts_ +6 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +1 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +1 -0
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +9 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +9 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentDeleteBulk.tsx +14 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +7 -1
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +6 -0
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +114 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/.metadata/index.ts +24 -3
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/.metadata/locales.ts +18 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/bean/bean.rbacResourceBulk.ts +50 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/bean/index.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/config/errors.ts +18 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/config/locale/en-us.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/config/locale/zh-cn.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/test/resourceBulk.test.ts +128 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/create/component/boilerplateTableActionBulk/controller.tsx_ +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +16 -12
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +66 -7
- package/zova/src/suite/a-training/modules/training-student/src/api/trainingStudent.ts +18 -0
- package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-commands/src/.metadata/index.ts +16 -0
- package/zova/src/suite/cabloy-basic/modules/basic-commands/src/bean/command.deleteBulk.tsx +35 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +143 -7
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +3 -2
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockToolbarBulk/controller.tsx +76 -4
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/config/locale/en-us.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/config/locale/zh-cn.ts +6 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/lib/selection.ts +84 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/test/lib/selection.test.ts +99 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/component/actionDeleteBulk.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionCreate/controller.tsx +5 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionDeleteBulk/controller.tsx +57 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +35 -7
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/en-us.ts +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/zh-cn.ts +4 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/lib/selectedIds.ts +15 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +57 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/lib/selectedIds.test.ts +20 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-command/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-command/src/lib/beanCommandSelectedBase.ts +33 -0
- package/zova/src/suite-vendor/a-zova/modules/a-command/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-command/src/types/command.ts +5 -0
- package/zova/src/suite-vendor/a-zova/modules/a-command/test/lib/beanCommandSelectedBase.test.ts +47 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +3 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/tableActionBulk.ts +19 -0
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +86 -26
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx +30 -6
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/types/table.ts +2 -0
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
|
@@ -18,6 +18,7 @@ import { ScopeModuleAOpenapi, SymbolOpenapiSchemaName } from 'zova-module-a-open
|
|
|
18
18
|
|
|
19
19
|
import { prepareDeleteRequestConfig } from '../lib/deleteRequest.js';
|
|
20
20
|
import { runMutationSuccess } from '../lib/mutationSuccess.js';
|
|
21
|
+
import { validateSelectedIds } from '../lib/selectedIds.js';
|
|
21
22
|
|
|
22
23
|
export interface IModelOptionsResource extends IDecoratorModelOptions {}
|
|
23
24
|
|
|
@@ -40,6 +41,14 @@ interface IModelResourceMutationItemOptions<TData = void, TVariables = void> {
|
|
|
40
41
|
invalidateSelect?: boolean;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
interface IModelResourceMutationSelectedOptions<TData = void, TVariables = void> {
|
|
45
|
+
action: string;
|
|
46
|
+
getIds: (variables: TVariables) => readonly TableIdentity[];
|
|
47
|
+
mutationFn: (params: TVariables) => Promise<TData>;
|
|
48
|
+
onSuccess?: (data: TData, variables: TVariables, context: unknown) => void | Promise<void>;
|
|
49
|
+
invalidateSelect?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
@Model<IModelOptionsResource>({
|
|
44
53
|
enableSelector: true,
|
|
45
54
|
})
|
|
@@ -187,6 +196,47 @@ export class ModelResource<
|
|
|
187
196
|
});
|
|
188
197
|
}
|
|
189
198
|
|
|
199
|
+
mutationSelected<TData = void, TVariables = void>(
|
|
200
|
+
options: IModelResourceMutationSelectedOptions<TData, TVariables>,
|
|
201
|
+
) {
|
|
202
|
+
const { action, getIds, mutationFn, onSuccess, invalidateSelect = true } = options;
|
|
203
|
+
return this.$useMutationData<TData, TVariables>({
|
|
204
|
+
mutationKey: ['selected', action, 'mutation'],
|
|
205
|
+
mutationFn: async variables => {
|
|
206
|
+
this._getMutationSelectedIds(getIds, variables);
|
|
207
|
+
return await mutationFn(variables);
|
|
208
|
+
},
|
|
209
|
+
onSuccess: async (data, variables, context) => {
|
|
210
|
+
const ids = this._getMutationSelectedIds(getIds, variables);
|
|
211
|
+
await runMutationSuccess({
|
|
212
|
+
invalidateSelect: invalidateSelect
|
|
213
|
+
? () => this.$invalidateQueries({ queryKey: ['select'] })
|
|
214
|
+
: undefined,
|
|
215
|
+
invalidateItem: async () => {
|
|
216
|
+
await Promise.all(
|
|
217
|
+
ids.map(id => this.$invalidateQueries({ queryKey: this.keyItemRoot(id) })),
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
onSuccess: () => onSuccess?.(data, variables, context),
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
deleteBulk() {
|
|
227
|
+
return this.mutationSelected<void, { ids: readonly TableIdentity[] }>({
|
|
228
|
+
action: 'delete',
|
|
229
|
+
getIds: body => body.ids,
|
|
230
|
+
mutationFn: async body => {
|
|
231
|
+
return this.$fetch.post<any, void, { ids: readonly TableIdentity[] }>(
|
|
232
|
+
this.sys.util.apiActionPathTranslate(`${this.resourceApi}/bulk/delete`),
|
|
233
|
+
body,
|
|
234
|
+
this.sys.util.apiActionConfigPrepare(),
|
|
235
|
+
);
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
190
240
|
update(id: TableIdentity) {
|
|
191
241
|
return this.mutationItem<void, EntityUpdate>({
|
|
192
242
|
id,
|
|
@@ -276,6 +326,13 @@ export class ModelResource<
|
|
|
276
326
|
return this.$sdk.getSchemaDefaultValue(schemaName) as EntityCreate | undefined;
|
|
277
327
|
}
|
|
278
328
|
|
|
329
|
+
private _getMutationSelectedIds<TVariables>(
|
|
330
|
+
getIds: (variables: TVariables) => readonly TableIdentity[],
|
|
331
|
+
variables: TVariables,
|
|
332
|
+
) {
|
|
333
|
+
return validateSelectedIds(getIds(variables));
|
|
334
|
+
}
|
|
335
|
+
|
|
279
336
|
protected keySelect(actionPath?: string, query?: ITableQuery) {
|
|
280
337
|
return ['select', actionPath ?? '', hashkey(query)] as const;
|
|
281
338
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { validateSelectedIds } from '../../src/lib/selectedIds.ts';
|
|
5
|
+
|
|
6
|
+
test('selected mutation IDs require a nonempty unique normalized collection', () => {
|
|
7
|
+
assert.deepEqual(validateSelectedIds([7, 'student-8']), [7, 'student-8']);
|
|
8
|
+
assert.throws(() => validateSelectedIds([]), /selected ids cannot empty/);
|
|
9
|
+
assert.throws(() => validateSelectedIds([undefined as never]), /selected row id cannot empty/);
|
|
10
|
+
assert.throws(() => validateSelectedIds(['']), /selected row id cannot empty/);
|
|
11
|
+
assert.throws(() => validateSelectedIds([1, '1']), /duplicate selected row id: 1/);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('selected mutation IDs do not impose a frontend count limit', () => {
|
|
15
|
+
const ids = Array.from({ length: 101 }, (_, index) => index + 1);
|
|
16
|
+
const validatedIds = validateSelectedIds(ids);
|
|
17
|
+
|
|
18
|
+
assert.deepEqual(validatedIds, ids);
|
|
19
|
+
assert.notEqual(validatedIds, ids);
|
|
20
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.52",
|
|
4
4
|
"gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"zova-module-a-rbac": "^5.0.1",
|
|
11
|
-
"zova-module-rest-resource": "^5.1.
|
|
11
|
+
"zova-module-rest-resource": "^5.1.51"
|
|
12
12
|
},
|
|
13
13
|
"title": "a-cabloy"
|
|
14
14
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
import type { IJsxRenderContextBase, IJsxRenderContextPage } from 'zova-module-a-openapi';
|
|
3
|
+
|
|
4
|
+
import { isNil } from '@cabloy/utils';
|
|
5
|
+
import { BeanBase } from 'zova';
|
|
6
|
+
|
|
7
|
+
import type { ICommandSelectedOptionsBase } from '../types/command.js';
|
|
8
|
+
|
|
9
|
+
export class BeanCommandSelectedBase extends BeanBase {
|
|
10
|
+
getResourceAndIds(
|
|
11
|
+
options: ICommandSelectedOptionsBase,
|
|
12
|
+
renderContext: IJsxRenderContextBase,
|
|
13
|
+
): { resource: string; ids: readonly TableIdentity[] } {
|
|
14
|
+
let resource: string | undefined = options.resource;
|
|
15
|
+
let ids = options.ids;
|
|
16
|
+
if (renderContext.$scene === 'page') {
|
|
17
|
+
const { $celScope } = renderContext as IJsxRenderContextPage;
|
|
18
|
+
resource = resource ?? $celScope.resource;
|
|
19
|
+
ids = ids ?? $celScope.selection?.ids;
|
|
20
|
+
}
|
|
21
|
+
if (isNil(resource) || !ids || ids.length === 0) {
|
|
22
|
+
throw new Error(`should specify resource and selected ids in scene: ${renderContext.$scene}`);
|
|
23
|
+
}
|
|
24
|
+
const keys = new Set<string>();
|
|
25
|
+
for (const id of ids) {
|
|
26
|
+
if (isNil(id) || id === '') throw new Error('selected row id cannot empty');
|
|
27
|
+
const key = String(id);
|
|
28
|
+
if (keys.has(key)) throw new Error(`duplicate selected row id: ${key}`);
|
|
29
|
+
keys.add(key);
|
|
30
|
+
}
|
|
31
|
+
return { resource, ids };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -40,6 +40,11 @@ export interface ICommandRowOptionsBase<Result = any> extends ICommandOptionsBas
|
|
|
40
40
|
id?: TableIdentity;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export interface ICommandSelectedOptionsBase<Result = any> extends ICommandOptionsBase<Result> {
|
|
44
|
+
resource?: string;
|
|
45
|
+
ids?: readonly TableIdentity[];
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
export interface ICommandDetailsRowOptionsBase<Result = any> extends ICommandOptionsBase<Result> {}
|
|
44
49
|
|
|
45
50
|
declare module 'zova-module-a-bean' {
|
package/zova/src/suite-vendor/a-zova/modules/a-command/test/lib/beanCommandSelectedBase.test.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { BeanCommandSelectedBase } from '../../src/lib/beanCommandSelectedBase.ts';
|
|
5
|
+
|
|
6
|
+
const command = Object.create(BeanCommandSelectedBase.prototype) as BeanCommandSelectedBase;
|
|
7
|
+
const renderContext = { $scene: 'app' } as any;
|
|
8
|
+
|
|
9
|
+
test('selected command IDs do not impose a frontend count limit', () => {
|
|
10
|
+
const ids = Array.from({ length: 101 }, (_, index) => index + 1);
|
|
11
|
+
|
|
12
|
+
assert.deepEqual(
|
|
13
|
+
command.getResourceAndIds({ resource: 'test:resource', ids } as any, renderContext),
|
|
14
|
+
{
|
|
15
|
+
resource: 'test:resource',
|
|
16
|
+
ids,
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('selected command IDs require a nonempty unique normalized collection', () => {
|
|
22
|
+
assert.throws(
|
|
23
|
+
() => command.getResourceAndIds({ resource: 'test:resource', ids: [] } as any, renderContext),
|
|
24
|
+
/should specify resource and selected ids/,
|
|
25
|
+
);
|
|
26
|
+
assert.throws(
|
|
27
|
+
() => command.getResourceAndIds({ ids: [1] } as any, renderContext),
|
|
28
|
+
/should specify resource and selected ids/,
|
|
29
|
+
);
|
|
30
|
+
assert.throws(
|
|
31
|
+
() =>
|
|
32
|
+
command.getResourceAndIds(
|
|
33
|
+
{ resource: 'test:resource', ids: [undefined as never] } as any,
|
|
34
|
+
renderContext,
|
|
35
|
+
),
|
|
36
|
+
/selected row id cannot empty/,
|
|
37
|
+
);
|
|
38
|
+
assert.throws(
|
|
39
|
+
() => command.getResourceAndIds({ resource: 'test:resource', ids: [''] } as any, renderContext),
|
|
40
|
+
/selected row id cannot empty/,
|
|
41
|
+
);
|
|
42
|
+
assert.throws(
|
|
43
|
+
() =>
|
|
44
|
+
command.getResourceAndIds({ resource: 'test:resource', ids: [1, '1'] } as any, renderContext),
|
|
45
|
+
/duplicate selected row id: 1/,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
@@ -3,6 +3,8 @@ import type { ZovaJsx } from 'zova-jsx';
|
|
|
3
3
|
|
|
4
4
|
import { TableIdentity } from 'table-identity';
|
|
5
5
|
|
|
6
|
+
import type { IResourceTableSelectionPayload } from './resource/tableActionBulk.js';
|
|
7
|
+
|
|
6
8
|
import { TypeOpenapiPermissions } from './permissions.js';
|
|
7
9
|
|
|
8
10
|
export interface IJsxRenderContextBase {
|
|
@@ -41,6 +43,7 @@ export interface IJsxRenderSceneRecord {
|
|
|
41
43
|
export interface IPageScope {
|
|
42
44
|
resource?: string;
|
|
43
45
|
permissions?: TypeOpenapiPermissions;
|
|
46
|
+
selection?: IResourceTableSelectionPayload;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface IJsxRenderContextPage extends IJsxRenderContextBase {
|
package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/tableActionBulk.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
|
|
1
3
|
import { types } from 'typestyle';
|
|
2
4
|
import { TypeRenderComponentJsx } from 'zova-jsx';
|
|
3
5
|
|
|
@@ -11,11 +13,28 @@ export type IResourceTableActionBulkNameRecord = {
|
|
|
11
13
|
|
|
12
14
|
export interface IResourceTableActionBulkRecord {}
|
|
13
15
|
|
|
16
|
+
export interface IResourceTableSelectionPayload<TData extends {} = Record<string, unknown>> {
|
|
17
|
+
ids: readonly TableIdentity[];
|
|
18
|
+
rows: readonly TData[];
|
|
19
|
+
count: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Declarative bulk-action metadata authored by a resource schema. */
|
|
14
23
|
export interface IResourceTableActionBulkOptionsBase {
|
|
15
24
|
class?: any;
|
|
16
25
|
style?: types.NestedCSSProperties;
|
|
17
26
|
resource?: string;
|
|
18
27
|
permission?: IPermissionHintTableActionBulk;
|
|
28
|
+
requiresSelection?: boolean;
|
|
29
|
+
selectedMaxIds?: number;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Runtime bulk-action props supplied by the owning toolbar for the current render. */
|
|
34
|
+
export interface IResourceTableActionBulkPropsBase extends IResourceTableActionBulkOptionsBase {
|
|
35
|
+
dynamicSelection?: IResourceTableSelectionPayload;
|
|
36
|
+
dynamicDisabled?: boolean;
|
|
37
|
+
dynamicDisabledReason?: string;
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
export interface IResourceRenderTableActionBulkOptionsAction {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getCoreRowModel,
|
|
15
15
|
OnChangeFn,
|
|
16
16
|
Row,
|
|
17
|
+
RowSelectionState,
|
|
17
18
|
SortingState,
|
|
18
19
|
TableOptionsWithReactiveData,
|
|
19
20
|
} from '@tanstack/vue-table';
|
|
@@ -46,6 +47,7 @@ import type {
|
|
|
46
47
|
} from '../../types/tableColumn.js';
|
|
47
48
|
|
|
48
49
|
import { BeanControllerTableBase } from '../../lib/beanControllerTableBase.js';
|
|
50
|
+
import { TableColumnIdSelection } from '../../types/table.js';
|
|
49
51
|
import { ITableCellRenderColumnOptions } from '../../types/tableColumn.js';
|
|
50
52
|
|
|
51
53
|
export interface ControllerTableProps<TData extends {} = {}> {
|
|
@@ -54,6 +56,9 @@ export interface ControllerTableProps<TData extends {} = {}> {
|
|
|
54
56
|
schemaOrder?: SchemaObject;
|
|
55
57
|
sorting?: SortingState;
|
|
56
58
|
onSortingChange?: OnChangeFn<SortingState>;
|
|
59
|
+
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
|
60
|
+
rowSelection?: RowSelectionState;
|
|
61
|
+
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
|
|
57
62
|
tableScope?: ITableScope;
|
|
58
63
|
getColumns?: TypeTableGetColumns<TData>;
|
|
59
64
|
slotDefault?: (table: ControllerTable<TData>) => VNode;
|
|
@@ -89,6 +94,13 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
|
|
|
89
94
|
await this.refreshMeta();
|
|
90
95
|
},
|
|
91
96
|
);
|
|
97
|
+
this.$watch(
|
|
98
|
+
() => this.$props.enableRowSelection,
|
|
99
|
+
async (newValue, oldValue) => {
|
|
100
|
+
if (newValue === oldValue) return;
|
|
101
|
+
await this.refreshMeta();
|
|
102
|
+
},
|
|
103
|
+
);
|
|
92
104
|
// table
|
|
93
105
|
this._createTable();
|
|
94
106
|
}
|
|
@@ -105,6 +117,10 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
|
|
|
105
117
|
return this.$props.sorting ?? [];
|
|
106
118
|
}
|
|
107
119
|
|
|
120
|
+
public get rowSelection() {
|
|
121
|
+
return this.$props.rowSelection ?? {};
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
public async refreshMeta() {
|
|
109
125
|
this.tableMeta = await this._createTableMeta();
|
|
110
126
|
this.columns = await this._createColumns();
|
|
@@ -115,22 +131,30 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
|
|
|
115
131
|
// eslint-disable-next-line
|
|
116
132
|
const self = this;
|
|
117
133
|
const tableOptions: TableOptionsWithReactiveData<TData> = {
|
|
118
|
-
getRowId:
|
|
134
|
+
getRowId: (originalRow, index, parent) => this._getRowId(originalRow, index, parent),
|
|
119
135
|
getCoreRowModel: getCoreRowModel(),
|
|
120
136
|
renderFallbackValue: this.scope.config.renderFallbackValue,
|
|
121
137
|
manualPagination: true,
|
|
122
138
|
manualSorting: true,
|
|
123
139
|
enableMultiSort: false,
|
|
124
140
|
enableSortingRemoval: false,
|
|
141
|
+
enableRowSelection: row => self._canSelectRow(row),
|
|
125
142
|
state: {
|
|
126
143
|
get sorting() {
|
|
127
144
|
return self.sorting;
|
|
128
145
|
},
|
|
146
|
+
get rowSelection() {
|
|
147
|
+
return self.rowSelection;
|
|
148
|
+
},
|
|
129
149
|
},
|
|
130
150
|
onSortingChange: updater => {
|
|
131
151
|
const sorting = functionalUpdate(updater, self.sorting).slice(0, 1);
|
|
132
152
|
self.$props.onSortingChange?.(sorting);
|
|
133
153
|
},
|
|
154
|
+
onRowSelectionChange: updater => {
|
|
155
|
+
const rowSelection = functionalUpdate(updater, self.rowSelection);
|
|
156
|
+
self.$props.onRowSelectionChange?.(rowSelection);
|
|
157
|
+
},
|
|
134
158
|
get initialState() {
|
|
135
159
|
const columnPinning = self._getColumnPinning();
|
|
136
160
|
return { columnPinning };
|
|
@@ -148,31 +172,50 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
|
|
|
148
172
|
|
|
149
173
|
private async _createColumns() {
|
|
150
174
|
if (!this.properties) return [];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
const columns = !this.$props.getColumns
|
|
176
|
+
? await this._createColumnsMiddle(this.tableMeta.properties)
|
|
177
|
+
: await this.$props.getColumns(
|
|
178
|
+
async properties => {
|
|
179
|
+
return await this._createColumnsMiddle(properties ?? this.tableMeta.properties);
|
|
180
|
+
},
|
|
181
|
+
async (
|
|
182
|
+
key: string,
|
|
183
|
+
render: TypeTableCellRenderComponent,
|
|
184
|
+
): Promise<TypeTableCellRender<TData, any> | undefined> => {
|
|
185
|
+
// columnScope
|
|
186
|
+
const columnScope = this.getColumnScope(key);
|
|
187
|
+
// renderContext
|
|
188
|
+
const jsxRenderContext = this.getColumnJsxRenderContext(columnScope);
|
|
189
|
+
// columnProps
|
|
190
|
+
const { visible, columnProps } = this.getColumnComponentPropsTop(
|
|
191
|
+
key,
|
|
192
|
+
columnScope,
|
|
193
|
+
jsxRenderContext,
|
|
194
|
+
);
|
|
195
|
+
// visible
|
|
196
|
+
if (visible === false) return;
|
|
197
|
+
return await this._createColumnRender(
|
|
198
|
+
render,
|
|
199
|
+
columnProps,
|
|
200
|
+
columnScope,
|
|
201
|
+
jsxRenderContext,
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
this,
|
|
169
205
|
);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
206
|
+
if (!this.$props.enableRowSelection) return columns;
|
|
207
|
+
const columnHelper = createColumnHelper<TData>();
|
|
208
|
+
return [
|
|
209
|
+
columnHelper.display({
|
|
210
|
+
id: TableColumnIdSelection,
|
|
211
|
+
header: '',
|
|
212
|
+
cell: '',
|
|
213
|
+
size: 48,
|
|
214
|
+
enableSorting: false,
|
|
215
|
+
meta: { rest: { fixed: 'left', width: 48, align: 'center' } },
|
|
216
|
+
}),
|
|
217
|
+
...columns,
|
|
218
|
+
];
|
|
176
219
|
}
|
|
177
220
|
|
|
178
221
|
private async _createColumnsMiddle(
|
|
@@ -200,8 +243,25 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
|
|
|
200
243
|
return columns;
|
|
201
244
|
}
|
|
202
245
|
|
|
246
|
+
private _getRowId(originalRow: TData, index: number, parent?: Row<TData>) {
|
|
247
|
+
const id = this.$props.getRowId?.(originalRow, index, parent) ?? cast(originalRow).id;
|
|
248
|
+
if (isNilOrEmptyString(id)) {
|
|
249
|
+
return `__unselectable_${parent ? `${parent.id}.` : ''}${index}`;
|
|
250
|
+
}
|
|
251
|
+
return String(id);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private _canSelectRow(row: Row<TData>) {
|
|
255
|
+
const enableRowSelection = this.$props.enableRowSelection;
|
|
256
|
+
if (!enableRowSelection) return false;
|
|
257
|
+
const originalId =
|
|
258
|
+
this.$props.getRowId?.(row.original, row.index, row.getParentRow()) ?? cast(row.original).id;
|
|
259
|
+
if (isNilOrEmptyString(originalId)) return false;
|
|
260
|
+
return typeof enableRowSelection === 'function' ? enableRowSelection(row) : true;
|
|
261
|
+
}
|
|
262
|
+
|
|
203
263
|
private _getColumnPinning() {
|
|
204
|
-
const left: string[] = [];
|
|
264
|
+
const left: string[] = this.$props.enableRowSelection ? [TableColumnIdSelection] : [];
|
|
205
265
|
const right: string[] = [];
|
|
206
266
|
for (const property of this.tableMeta?.properties ?? []) {
|
|
207
267
|
const fixed = property.rest?.fixed;
|
|
@@ -4,6 +4,8 @@ import { FlexRender } from '@tanstack/vue-table';
|
|
|
4
4
|
import { BeanRenderBase } from 'zova';
|
|
5
5
|
import { Render } from 'zova-module-a-bean';
|
|
6
6
|
|
|
7
|
+
import { TableColumnIdSelection } from '../../types/table.js';
|
|
8
|
+
|
|
7
9
|
@Render()
|
|
8
10
|
export class RenderTable extends BeanRenderBase {
|
|
9
11
|
private _getColumnStyle(column: any): CSSProperties {
|
|
@@ -33,7 +35,18 @@ export class RenderTable extends BeanRenderBase {
|
|
|
33
35
|
const sorted = column.getIsSorted();
|
|
34
36
|
const fixed = column.getIsPinned();
|
|
35
37
|
const headerDefinition = column.columnDef.header;
|
|
36
|
-
const
|
|
38
|
+
const selection = column.id === TableColumnIdSelection;
|
|
39
|
+
const headerContent = selection ? (
|
|
40
|
+
<input
|
|
41
|
+
type="checkbox"
|
|
42
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
43
|
+
indeterminate={table.getIsSomePageRowsSelected()}
|
|
44
|
+
aria-checked={table.getIsSomePageRowsSelected() ? 'mixed' : undefined}
|
|
45
|
+
aria-label="Select all rows on this page"
|
|
46
|
+
disabled={table.getRowModel().rows.every(row => !row.getCanSelect())}
|
|
47
|
+
onChange={table.getToggleAllPageRowsSelectedHandler()}
|
|
48
|
+
/>
|
|
49
|
+
) : (
|
|
37
50
|
<FlexRender render={headerDefinition} props={header.getContext()}></FlexRender>
|
|
38
51
|
);
|
|
39
52
|
let sortAria: AriaAttributes['aria-sort'] = 'none';
|
|
@@ -73,19 +86,30 @@ export class RenderTable extends BeanRenderBase {
|
|
|
73
86
|
<tbody>
|
|
74
87
|
{table.getRowModel().rows.map(row => {
|
|
75
88
|
return (
|
|
76
|
-
<tr key={row.id}>
|
|
89
|
+
<tr key={row.id} aria-selected={row.getIsSelected()}>
|
|
77
90
|
{row.getVisibleCells().map(cell => {
|
|
78
91
|
const column = cell.column;
|
|
92
|
+
const selection = column.id === TableColumnIdSelection;
|
|
79
93
|
return (
|
|
80
94
|
<td
|
|
81
95
|
key={cell.id}
|
|
82
96
|
class={column.getIsPinned() ? 'bg-base-100' : undefined}
|
|
83
97
|
style={this._getColumnStyle(column)}
|
|
84
98
|
>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
{selection ? (
|
|
100
|
+
<input
|
|
101
|
+
type="checkbox"
|
|
102
|
+
checked={row.getIsSelected()}
|
|
103
|
+
disabled={!row.getCanSelect()}
|
|
104
|
+
aria-label={`Select row ${row.id}`}
|
|
105
|
+
onChange={row.getToggleSelectedHandler()}
|
|
106
|
+
/>
|
|
107
|
+
) : (
|
|
108
|
+
<FlexRender
|
|
109
|
+
render={column.columnDef.cell}
|
|
110
|
+
props={cell.getContext()}
|
|
111
|
+
></FlexRender>
|
|
112
|
+
)}
|
|
89
113
|
</td>
|
|
90
114
|
);
|
|
91
115
|
})}
|
|
@@ -11,6 +11,8 @@ export type TypeTable<TData extends {} = {}> = ReturnType<typeof useVueTable<TDa
|
|
|
11
11
|
|
|
12
12
|
export type TypeColumn<TData extends {} = {}> = ColumnDef<TData, any>;
|
|
13
13
|
|
|
14
|
+
export const TableColumnIdSelection = '$$selection';
|
|
15
|
+
|
|
14
16
|
export interface ITableMeta<TData extends {} = {}> {
|
|
15
17
|
properties: ISchemaObjectExtensionField[];
|
|
16
18
|
renders: Record<string, TypeTableCellRender<TData>>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.156",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "zova",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"zova-module-a-behavior": "^5.1.26",
|
|
15
15
|
"zova-module-a-behaviors": "^5.1.22",
|
|
16
16
|
"zova-module-a-boundary": "^5.1.22",
|
|
17
|
-
"zova-module-a-command": "^5.1.
|
|
17
|
+
"zova-module-a-command": "^5.1.35",
|
|
18
18
|
"zova-module-a-fetch": "^5.1.25",
|
|
19
19
|
"zova-module-a-form": "^5.1.51",
|
|
20
20
|
"zova-module-a-icon": "^5.1.27",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"zova-module-a-logger": "^5.1.28",
|
|
23
23
|
"zova-module-a-meta": "^5.1.22",
|
|
24
24
|
"zova-module-a-model": "^5.1.36",
|
|
25
|
-
"zova-module-a-openapi": "^5.1.
|
|
25
|
+
"zova-module-a-openapi": "^5.1.53",
|
|
26
26
|
"zova-module-a-router": "^5.1.34",
|
|
27
27
|
"zova-module-a-routerstack": "^5.1.27",
|
|
28
28
|
"zova-module-a-routertabs": "^5.1.34",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"zova-module-a-ssrhmr": "^5.1.23",
|
|
31
31
|
"zova-module-a-ssrserver": "^5.1.26",
|
|
32
32
|
"zova-module-a-style": "^5.1.35",
|
|
33
|
-
"zova-module-a-table": "^5.1.
|
|
33
|
+
"zova-module-a-table": "^5.1.40",
|
|
34
34
|
"zova-module-a-zod": "^5.1.37",
|
|
35
35
|
"zova-module-a-zova": "^5.1.103"
|
|
36
36
|
},
|