@wordrhyme/auto-crud 1.17.1 → 1.18.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/README.md +28 -2
- package/dist/index.cjs +86 -7
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +86 -7
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1939,7 +1939,7 @@ container resizing and translated text widths, without changing other tables.
|
|
|
1939
1939
|
|
|
1940
1940
|
## 保存视图与动态标签
|
|
1941
1941
|
|
|
1942
|
-
|
|
1942
|
+
视图默认关闭。在 `QueryClientProvider` 内配置存储服务后,通过 `table={{ views: true }}` 为带稳定 `id` 的 `AutoCrudTable` 开启视图菜单:
|
|
1943
1943
|
|
|
1944
1944
|
```tsx
|
|
1945
1945
|
import {
|
|
@@ -1959,6 +1959,7 @@ const viewStorage = createLocalViewStorage(`${organizationId}:${userId}`);
|
|
|
1959
1959
|
>
|
|
1960
1960
|
<AutoCrudTable
|
|
1961
1961
|
id="crm.customers"
|
|
1962
|
+
table={{ views: true }}
|
|
1962
1963
|
schema={schema}
|
|
1963
1964
|
resource={resource}
|
|
1964
1965
|
fields={{
|
|
@@ -1969,7 +1970,7 @@ const viewStorage = createLocalViewStorage(`${organizationId}:${userId}`);
|
|
|
1969
1970
|
</QueryClientProvider>;
|
|
1970
1971
|
```
|
|
1971
1972
|
|
|
1972
|
-
- 视图保存筛选条件、条件间的 AND/OR
|
|
1973
|
+
- 视图保存筛选条件、条件间的 AND/OR、搜索词和排序;不保存分页、选中行。支持新建、复制、重命名、删除、保存和放弃修改。省略 `views` 或传 `false` 时关闭,传 `true` 或配置对象时开启。独立 `AutoTable` 使用 `views={true}`,并提供稳定的 `resourceId`。全局 `viewStorage` 和本地展示偏好不会自动开启视图。之前依赖自动开启的列表需补充 `table={{ views: true }}`。
|
|
1973
1974
|
- 视图支持下拉与 Tab 两种展示:下拉放在搜索框前,设置收在下拉菜单内;Tab 放在筛选栏上方,末尾仅提供设置图标,新建操作与下拉模式一致,放在设置菜单内。在设置菜单的“展示方式”中切换,当前视图及未保存修改会保留。有修改时显示状态点、“保存修改”和“还原”。无本地偏好时默认使用下拉;`table={{ views: { selector: 'tabs' } }}` 可设置初始展示。独立使用 `AutoTable` 时传 `views={{ selector: 'tabs' }}`。本地记忆优先于 `selector`,展示方式不写入已保存的查询。
|
|
1974
1975
|
- `scope` 必须包含当前用户与租户;切换时重置组件状态。`createLocalViewStorage` 仅保存当前浏览器中的视图。换用后端时传入 `ViewStorage`,组件调用方式不变。
|
|
1975
1976
|
- `TagService.list` 接收 `resourceId/field` 和可选的 `query/page/pageSize/signal`,统一返回 `{ options: TagOption[], hasMore: boolean }`,两个字段都必须提供。每个选项包含稳定 ID `value`、名称 `label` 和颜色 `color`。`resolve` 返回指定稳定 ID 对应的名称与颜色。用户新建标签只增加选项,不增加 schema 字段。
|
|
@@ -2063,6 +2064,31 @@ Storybook 的 **Auto CRUD / Saved views and tags / Tabs With Counts** 使用内
|
|
|
2063
2064
|
|
|
2064
2065
|
Storybook 提供 **Line Tabs**(下方蓝色强调线)和 **Top Line Tabs**(上方强调线)示例,均带视图数量;可通过 Controls 切换样式和线条位置。已有本地展示偏好时,仍可在视图设置中切换到 Tab。
|
|
2065
2066
|
|
|
2067
|
+
### 自动标签接入
|
|
2068
|
+
|
|
2069
|
+
字段声明 `tags: true` 后,`AutoCrudTable` 自动展示标签、启用标签筛选,并生成行操作和批量操作的设置入口:
|
|
2070
|
+
|
|
2071
|
+
```tsx
|
|
2072
|
+
const schema = z.object({
|
|
2073
|
+
id: z.string(),
|
|
2074
|
+
recordTags: z.array(z.string()).optional(),
|
|
2075
|
+
});
|
|
2076
|
+
|
|
2077
|
+
<AutoCrudTable
|
|
2078
|
+
id="crm.customers"
|
|
2079
|
+
schema={schema}
|
|
2080
|
+
resource={resource}
|
|
2081
|
+
permissions={permissions}
|
|
2082
|
+
fields={{ recordTags: { label: '标签', form: false, tags: true } }}
|
|
2083
|
+
/>;
|
|
2084
|
+
```
|
|
2085
|
+
|
|
2086
|
+
应用级 `AutoCrudProvider` 提供 `tagService`,页面无需再维护 `TagAssignment`、弹窗状态或选中记录转换。资源 ID 复用表格 `id`,记录主键复用 `resource.idKey`(默认 `id`),保存后调用 `resource.handlers.refresh`。未填写的标签值按空数组处理。
|
|
2087
|
+
|
|
2088
|
+
自动入口跟随 `permissions.can.update`,字段 `hidden: true` 或被 `permissions.deny` 禁止时不生成入口;服务未提供 `assign` 时只展示和筛选。已有行操作、批量操作和注册扩展仍会保留。自动操作的稳定 ID 为 `tags:<字段名>`,可通过现有操作注册机制调整。`locale.tagAssignment` 可覆盖入口与弹窗标题。
|
|
2089
|
+
|
|
2090
|
+
`tags: true` 默认补充 `filter.variant: 'tags'`,显式筛选配置优先,`filter: false` 仍可禁用筛选且保留标签展示和设置入口。省略或设置 `tags: false` 不生成自动入口,已有 `filter.variant: 'tags'` 和手动弹窗接入方式保持兼容。标签仍由现有服务读写,无需新增页面接口或业务表。
|
|
2091
|
+
|
|
2066
2092
|
### 自定义标签编辑入口
|
|
2067
2093
|
|
|
2068
2094
|
`TagAssignment` 是受控弹窗,不包含按钮。打开后直接显示搜索和标签列表,已有标签默认选中,通过勾选/取消勾选编辑,无需选择添加或移除。已选标签的名称、颜色实时显示;清空和保存位于同一行,点击保存后才提交记录的标签关联。
|
package/dist/index.cjs
CHANGED
|
@@ -2184,6 +2184,7 @@ const enUSTableControls = {
|
|
|
2184
2184
|
}
|
|
2185
2185
|
};
|
|
2186
2186
|
const zhCN = {
|
|
2187
|
+
tagAssignment: (label) => `设置${label}`,
|
|
2187
2188
|
batchSearch: zhCNBatchSearch,
|
|
2188
2189
|
tableControls: {
|
|
2189
2190
|
sort: {
|
|
@@ -2299,6 +2300,7 @@ const zhCN = {
|
|
|
2299
2300
|
}
|
|
2300
2301
|
};
|
|
2301
2302
|
const enUS = {
|
|
2303
|
+
tagAssignment: (label) => `Set ${label}`,
|
|
2302
2304
|
batchSearch: enUSBatchSearch,
|
|
2303
2305
|
tableControls: enUSTableControls,
|
|
2304
2306
|
pagination: enUSPagination,
|
|
@@ -7341,8 +7343,7 @@ const filterModeConfig = {
|
|
|
7341
7343
|
function AutoTable({ resourceId, views, schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, queryKeys, initialColumnVisibility, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, batchSearch, filterState, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarLocale, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows, exportTableRef }) {
|
|
7342
7344
|
const inheritedResourceId = (0, react.useContext)(CrudResourceContext);
|
|
7343
7345
|
const activeResourceId = resourceId ?? inheritedResourceId;
|
|
7344
|
-
const
|
|
7345
|
-
const viewsEnabled = views ?? (!!activeResourceId && !!services?.viewStorage);
|
|
7346
|
+
const viewsEnabled = views ?? false;
|
|
7346
7347
|
if (viewsEnabled && !activeResourceId) throw new Error("Saved views require a stable resourceId");
|
|
7347
7348
|
if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
|
|
7348
7349
|
const enableExpansion = !!expandedRow;
|
|
@@ -8049,7 +8050,7 @@ function mergeFieldPart(base, override) {
|
|
|
8049
8050
|
};
|
|
8050
8051
|
}
|
|
8051
8052
|
function mergeFieldConfig(base, override) {
|
|
8052
|
-
|
|
8053
|
+
const config = {
|
|
8053
8054
|
...base,
|
|
8054
8055
|
...override,
|
|
8055
8056
|
enum: override?.enum ?? base?.enum,
|
|
@@ -8058,6 +8059,11 @@ function mergeFieldConfig(base, override) {
|
|
|
8058
8059
|
filter: mergeFieldPart(base?.filter, override?.filter),
|
|
8059
8060
|
form: mergeFieldPart(base?.form, override?.form)
|
|
8060
8061
|
};
|
|
8062
|
+
if (config.tags && config.filter !== false) config.filter = {
|
|
8063
|
+
variant: "tags",
|
|
8064
|
+
...config.filter
|
|
8065
|
+
};
|
|
8066
|
+
return config;
|
|
8061
8067
|
}
|
|
8062
8068
|
function mergeFields(base, override) {
|
|
8063
8069
|
if (!base && !override) return void 0;
|
|
@@ -10130,7 +10136,7 @@ function resolveActions(actions, defaults, rowActionsLocale, context) {
|
|
|
10130
10136
|
*/
|
|
10131
10137
|
function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate, onExport }) {
|
|
10132
10138
|
const services = useAutoCrudConfig();
|
|
10133
|
-
const viewsEnabled = tableConfig?.views ??
|
|
10139
|
+
const viewsEnabled = tableConfig?.views ?? false;
|
|
10134
10140
|
if (viewsEnabled && !id) throw new Error("Saved views require a stable AutoCrudTable id");
|
|
10135
10141
|
const locale = react.useMemo(() => resolveLocale(localeProp), [localeProp]);
|
|
10136
10142
|
const getPagination = react.useCallback(() => id ? tablePagination.get(id) : void 0, [id]);
|
|
@@ -10175,6 +10181,51 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10175
10181
|
const excludedColumns = react.useMemo(() => hiddenColumns.filter((key) => !filterOnly.includes(key)), [hiddenColumns, filterOnly]);
|
|
10176
10182
|
const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
|
|
10177
10183
|
const resourceIdKey = resource.idKey ?? "id";
|
|
10184
|
+
const tagFields = react.useMemo(() => Object.entries(resolvedFields).filter(([field, config]) => config.tags && !config.hidden && !denyFields?.includes(field)).map(([field, config]) => {
|
|
10185
|
+
const label = config.label ?? humanize(field);
|
|
10186
|
+
return {
|
|
10187
|
+
field,
|
|
10188
|
+
actionId: `tags:${field}`,
|
|
10189
|
+
label: locale.tagAssignment?.(label) ?? `${locale.rowActions.edit} ${label}`
|
|
10190
|
+
};
|
|
10191
|
+
}), [
|
|
10192
|
+
resolvedFields,
|
|
10193
|
+
denyFields,
|
|
10194
|
+
locale
|
|
10195
|
+
]);
|
|
10196
|
+
if (tagFields.length && (!id || !services?.tagService)) throw new Error("Tag fields require a CRUD id and AutoCrudProvider.tagService");
|
|
10197
|
+
const [tagAssignment, setTagAssignment] = react.useState(null);
|
|
10198
|
+
const canAssignTags = can.update && Boolean(services?.tagService?.assign);
|
|
10199
|
+
const tagAssignmentVisible = canAssignTags && tagAssignment?.resourceId === id && tagFields.some(({ field }) => field === tagAssignment?.field);
|
|
10200
|
+
react.useEffect(() => {
|
|
10201
|
+
if (!tagAssignmentVisible) setTagAssignment(null);
|
|
10202
|
+
}, [tagAssignmentVisible]);
|
|
10203
|
+
const openTagAssignment = react.useCallback((field, dialogTitle, rows) => {
|
|
10204
|
+
if (!id || !canAssignTags || !rows.length) return;
|
|
10205
|
+
const entities = [];
|
|
10206
|
+
for (const row of rows) {
|
|
10207
|
+
const rowId = readRowId(row, resourceIdKey);
|
|
10208
|
+
const tagIds = row[field] ?? [];
|
|
10209
|
+
if (!rowId || !Array.isArray(tagIds) || !tagIds.every((tagId) => typeof tagId === "string")) {
|
|
10210
|
+
sonner.toast.error("Tag assignment requires a record id and an array of tag ids");
|
|
10211
|
+
return;
|
|
10212
|
+
}
|
|
10213
|
+
entities.push({
|
|
10214
|
+
id: rowId,
|
|
10215
|
+
tagIds: [...tagIds]
|
|
10216
|
+
});
|
|
10217
|
+
}
|
|
10218
|
+
setTagAssignment({
|
|
10219
|
+
resourceId: id,
|
|
10220
|
+
field,
|
|
10221
|
+
title: dialogTitle,
|
|
10222
|
+
entities
|
|
10223
|
+
});
|
|
10224
|
+
}, [
|
|
10225
|
+
id,
|
|
10226
|
+
canAssignTags,
|
|
10227
|
+
resourceIdKey
|
|
10228
|
+
]);
|
|
10178
10229
|
const actionRegistryVersion = useCrudActionsVersion();
|
|
10179
10230
|
const unifiedActions = react.useMemo(() => {
|
|
10180
10231
|
if (isUnifiedActionsConfig(actionItems)) return actionItems;
|
|
@@ -10184,8 +10235,28 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10184
10235
|
const ownerRowConfig = unifiedActions.row;
|
|
10185
10236
|
const ownerBatchConfig = unifiedActions.batch ?? tableConfig?.batchActions;
|
|
10186
10237
|
const ownerToolbarActions = react.useMemo(() => resolveOwnerToolbarActions(ownerToolbarConfig), [ownerToolbarConfig]);
|
|
10187
|
-
const ownerRowActions = react.useMemo(() => resolveOwnerRowActions(ownerRowConfig),
|
|
10188
|
-
|
|
10238
|
+
const ownerRowActions = react.useMemo(() => [...resolveOwnerRowActions(ownerRowConfig), ...canAssignTags ? tagFields.map(({ field, actionId, label }) => ({
|
|
10239
|
+
type: "custom",
|
|
10240
|
+
id: actionId,
|
|
10241
|
+
label,
|
|
10242
|
+
onClick: (row) => openTagAssignment(field, label, [row])
|
|
10243
|
+
})) : []], [
|
|
10244
|
+
ownerRowConfig,
|
|
10245
|
+
canAssignTags,
|
|
10246
|
+
tagFields,
|
|
10247
|
+
openTagAssignment
|
|
10248
|
+
]);
|
|
10249
|
+
const ownerBatchActions = react.useMemo(() => [...resolveOwnerBatchActions(ownerBatchConfig), ...canAssignTags ? tagFields.map(({ field, actionId, label }) => ({
|
|
10250
|
+
type: "custom",
|
|
10251
|
+
id: actionId,
|
|
10252
|
+
label,
|
|
10253
|
+
onClick: (rows) => openTagAssignment(field, label, rows)
|
|
10254
|
+
})) : []], [
|
|
10255
|
+
ownerBatchConfig,
|
|
10256
|
+
canAssignTags,
|
|
10257
|
+
tagFields,
|
|
10258
|
+
openTagAssignment
|
|
10259
|
+
]);
|
|
10189
10260
|
const registryToolbarActions = react.useMemo(() => crudActions.resolve(id, "toolbar", ownerToolbarActions), [
|
|
10190
10261
|
id,
|
|
10191
10262
|
ownerToolbarActions,
|
|
@@ -10375,7 +10446,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10375
10446
|
}
|
|
10376
10447
|
}
|
|
10377
10448
|
for (const [field, config] of Object.entries(resolvedFields)) {
|
|
10378
|
-
if (!config.filter || config.filter.variant !== "tags" || formattedOverrides[field]?.cell) continue;
|
|
10449
|
+
if (!config.tags && (!config.filter || config.filter.variant !== "tags") || formattedOverrides[field]?.cell) continue;
|
|
10379
10450
|
formattedOverrides[field] = {
|
|
10380
10451
|
...formattedOverrides[field],
|
|
10381
10452
|
enableSorting: formattedOverrides[field]?.enableSorting ?? false,
|
|
@@ -10625,6 +10696,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10625
10696
|
...onExport ? { exportTableRef } : {}
|
|
10626
10697
|
}),
|
|
10627
10698
|
rowActionDialog,
|
|
10699
|
+
tagAssignmentVisible && tagAssignment && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TagAssignment, {
|
|
10700
|
+
...tagAssignment,
|
|
10701
|
+
open: true,
|
|
10702
|
+
onOpenChange: (open) => {
|
|
10703
|
+
if (!open) setTagAssignment(null);
|
|
10704
|
+
},
|
|
10705
|
+
onSaved: resource.handlers.refresh
|
|
10706
|
+
}),
|
|
10628
10707
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudFormModal, {
|
|
10629
10708
|
open: resource.modal.createOpen || resource.modal.editOpen,
|
|
10630
10709
|
onOpenChange: (open) => !open && resource.handlers.closeModals(),
|
package/dist/index.d.cts
CHANGED
|
@@ -465,6 +465,8 @@ interface TableActionBarLocale {
|
|
|
465
465
|
clearSelection: string;
|
|
466
466
|
}
|
|
467
467
|
interface AutoCrudLocale {
|
|
468
|
+
/** Label shared by automatic tag assignment actions and their dialog. */
|
|
469
|
+
tagAssignment?: (fieldLabel: string) => string;
|
|
468
470
|
batchSearch?: BatchSearchLocale;
|
|
469
471
|
/** Sorting and column visibility controls. Optional for existing custom locales. */
|
|
470
472
|
tableControls?: TableControlsLocale;
|
|
@@ -1508,7 +1510,7 @@ interface TableSearchConfig {
|
|
|
1508
1510
|
interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<DataTableProps<z.infer<T>>, 'expandedRow'> {
|
|
1509
1511
|
/** Stable CRUD identity for saved views and tag services. */
|
|
1510
1512
|
resourceId?: string;
|
|
1511
|
-
/**
|
|
1513
|
+
/** Disabled by default. Pass true or a config to enable; requires viewStorage and a resourceId. */
|
|
1512
1514
|
views?: boolean | SavedViewsConfig;
|
|
1513
1515
|
schema: T;
|
|
1514
1516
|
data: z.infer<T>[];
|
|
@@ -1725,6 +1727,8 @@ type FieldTableDisplay = 'auto' | 'text' | 'badge' | 'date' | 'datetime';
|
|
|
1725
1727
|
interface Field {
|
|
1726
1728
|
/** 字段标签(表格和表单共用) */
|
|
1727
1729
|
label?: string;
|
|
1730
|
+
/** Enable tag display, filtering and assignment actions using the table id and resource idKey. */
|
|
1731
|
+
tags?: boolean;
|
|
1728
1732
|
/** Format default table/detail text and CSV values; undefined keeps the default. */
|
|
1729
1733
|
format?: (value: unknown, context: {
|
|
1730
1734
|
row: Record<string, unknown>;
|
|
@@ -1925,7 +1929,7 @@ interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
|
|
|
1925
1929
|
};
|
|
1926
1930
|
/** 表格配置 */
|
|
1927
1931
|
table?: {
|
|
1928
|
-
/**
|
|
1932
|
+
/** Disabled by default. Pass true or a config to enable; requires viewStorage and a stable id. */
|
|
1929
1933
|
views?: AutoTableProps<TSchema>['views'];
|
|
1930
1934
|
/** Optional controls; overrides the application registration for this CRUD id. */
|
|
1931
1935
|
pagination?: TablePaginationOptions;
|
package/dist/index.d.ts
CHANGED
|
@@ -465,6 +465,8 @@ interface TableActionBarLocale {
|
|
|
465
465
|
clearSelection: string;
|
|
466
466
|
}
|
|
467
467
|
interface AutoCrudLocale {
|
|
468
|
+
/** Label shared by automatic tag assignment actions and their dialog. */
|
|
469
|
+
tagAssignment?: (fieldLabel: string) => string;
|
|
468
470
|
batchSearch?: BatchSearchLocale;
|
|
469
471
|
/** Sorting and column visibility controls. Optional for existing custom locales. */
|
|
470
472
|
tableControls?: TableControlsLocale;
|
|
@@ -1508,7 +1510,7 @@ interface TableSearchConfig {
|
|
|
1508
1510
|
interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<DataTableProps<z.infer<T>>, 'expandedRow'> {
|
|
1509
1511
|
/** Stable CRUD identity for saved views and tag services. */
|
|
1510
1512
|
resourceId?: string;
|
|
1511
|
-
/**
|
|
1513
|
+
/** Disabled by default. Pass true or a config to enable; requires viewStorage and a resourceId. */
|
|
1512
1514
|
views?: boolean | SavedViewsConfig;
|
|
1513
1515
|
schema: T;
|
|
1514
1516
|
data: z.infer<T>[];
|
|
@@ -1725,6 +1727,8 @@ type FieldTableDisplay = 'auto' | 'text' | 'badge' | 'date' | 'datetime';
|
|
|
1725
1727
|
interface Field {
|
|
1726
1728
|
/** 字段标签(表格和表单共用) */
|
|
1727
1729
|
label?: string;
|
|
1730
|
+
/** Enable tag display, filtering and assignment actions using the table id and resource idKey. */
|
|
1731
|
+
tags?: boolean;
|
|
1728
1732
|
/** Format default table/detail text and CSV values; undefined keeps the default. */
|
|
1729
1733
|
format?: (value: unknown, context: {
|
|
1730
1734
|
row: Record<string, unknown>;
|
|
@@ -1925,7 +1929,7 @@ interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
|
|
|
1925
1929
|
};
|
|
1926
1930
|
/** 表格配置 */
|
|
1927
1931
|
table?: {
|
|
1928
|
-
/**
|
|
1932
|
+
/** Disabled by default. Pass true or a config to enable; requires viewStorage and a stable id. */
|
|
1929
1933
|
views?: AutoTableProps<TSchema>['views'];
|
|
1930
1934
|
/** Optional controls; overrides the application registration for this CRUD id. */
|
|
1931
1935
|
pagination?: TablePaginationOptions;
|
package/dist/index.js
CHANGED
|
@@ -2168,6 +2168,7 @@ const enUSTableControls = {
|
|
|
2168
2168
|
}
|
|
2169
2169
|
};
|
|
2170
2170
|
const zhCN = {
|
|
2171
|
+
tagAssignment: (label) => `设置${label}`,
|
|
2171
2172
|
batchSearch: zhCNBatchSearch,
|
|
2172
2173
|
tableControls: {
|
|
2173
2174
|
sort: {
|
|
@@ -2283,6 +2284,7 @@ const zhCN = {
|
|
|
2283
2284
|
}
|
|
2284
2285
|
};
|
|
2285
2286
|
const enUS = {
|
|
2287
|
+
tagAssignment: (label) => `Set ${label}`,
|
|
2286
2288
|
batchSearch: enUSBatchSearch,
|
|
2287
2289
|
tableControls: enUSTableControls,
|
|
2288
2290
|
pagination: enUSPagination,
|
|
@@ -7325,8 +7327,7 @@ const filterModeConfig = {
|
|
|
7325
7327
|
function AutoTable({ resourceId, views, schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, queryKeys, initialColumnVisibility, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, batchSearch, filterState, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarLocale, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows, exportTableRef }) {
|
|
7326
7328
|
const inheritedResourceId = useContext(CrudResourceContext);
|
|
7327
7329
|
const activeResourceId = resourceId ?? inheritedResourceId;
|
|
7328
|
-
const
|
|
7329
|
-
const viewsEnabled = views ?? (!!activeResourceId && !!services?.viewStorage);
|
|
7330
|
+
const viewsEnabled = views ?? false;
|
|
7330
7331
|
if (viewsEnabled && !activeResourceId) throw new Error("Saved views require a stable resourceId");
|
|
7331
7332
|
if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
|
|
7332
7333
|
const enableExpansion = !!expandedRow;
|
|
@@ -8033,7 +8034,7 @@ function mergeFieldPart(base, override) {
|
|
|
8033
8034
|
};
|
|
8034
8035
|
}
|
|
8035
8036
|
function mergeFieldConfig(base, override) {
|
|
8036
|
-
|
|
8037
|
+
const config = {
|
|
8037
8038
|
...base,
|
|
8038
8039
|
...override,
|
|
8039
8040
|
enum: override?.enum ?? base?.enum,
|
|
@@ -8042,6 +8043,11 @@ function mergeFieldConfig(base, override) {
|
|
|
8042
8043
|
filter: mergeFieldPart(base?.filter, override?.filter),
|
|
8043
8044
|
form: mergeFieldPart(base?.form, override?.form)
|
|
8044
8045
|
};
|
|
8046
|
+
if (config.tags && config.filter !== false) config.filter = {
|
|
8047
|
+
variant: "tags",
|
|
8048
|
+
...config.filter
|
|
8049
|
+
};
|
|
8050
|
+
return config;
|
|
8045
8051
|
}
|
|
8046
8052
|
function mergeFields(base, override) {
|
|
8047
8053
|
if (!base && !override) return void 0;
|
|
@@ -10114,7 +10120,7 @@ function resolveActions(actions, defaults, rowActionsLocale, context) {
|
|
|
10114
10120
|
*/
|
|
10115
10121
|
function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate, onExport }) {
|
|
10116
10122
|
const services = useAutoCrudConfig();
|
|
10117
|
-
const viewsEnabled = tableConfig?.views ??
|
|
10123
|
+
const viewsEnabled = tableConfig?.views ?? false;
|
|
10118
10124
|
if (viewsEnabled && !id) throw new Error("Saved views require a stable AutoCrudTable id");
|
|
10119
10125
|
const locale = React.useMemo(() => resolveLocale(localeProp), [localeProp]);
|
|
10120
10126
|
const getPagination = React.useCallback(() => id ? tablePagination.get(id) : void 0, [id]);
|
|
@@ -10159,6 +10165,51 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10159
10165
|
const excludedColumns = React.useMemo(() => hiddenColumns.filter((key) => !filterOnly.includes(key)), [hiddenColumns, filterOnly]);
|
|
10160
10166
|
const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
|
|
10161
10167
|
const resourceIdKey = resource.idKey ?? "id";
|
|
10168
|
+
const tagFields = React.useMemo(() => Object.entries(resolvedFields).filter(([field, config]) => config.tags && !config.hidden && !denyFields?.includes(field)).map(([field, config]) => {
|
|
10169
|
+
const label = config.label ?? humanize(field);
|
|
10170
|
+
return {
|
|
10171
|
+
field,
|
|
10172
|
+
actionId: `tags:${field}`,
|
|
10173
|
+
label: locale.tagAssignment?.(label) ?? `${locale.rowActions.edit} ${label}`
|
|
10174
|
+
};
|
|
10175
|
+
}), [
|
|
10176
|
+
resolvedFields,
|
|
10177
|
+
denyFields,
|
|
10178
|
+
locale
|
|
10179
|
+
]);
|
|
10180
|
+
if (tagFields.length && (!id || !services?.tagService)) throw new Error("Tag fields require a CRUD id and AutoCrudProvider.tagService");
|
|
10181
|
+
const [tagAssignment, setTagAssignment] = React.useState(null);
|
|
10182
|
+
const canAssignTags = can.update && Boolean(services?.tagService?.assign);
|
|
10183
|
+
const tagAssignmentVisible = canAssignTags && tagAssignment?.resourceId === id && tagFields.some(({ field }) => field === tagAssignment?.field);
|
|
10184
|
+
React.useEffect(() => {
|
|
10185
|
+
if (!tagAssignmentVisible) setTagAssignment(null);
|
|
10186
|
+
}, [tagAssignmentVisible]);
|
|
10187
|
+
const openTagAssignment = React.useCallback((field, dialogTitle, rows) => {
|
|
10188
|
+
if (!id || !canAssignTags || !rows.length) return;
|
|
10189
|
+
const entities = [];
|
|
10190
|
+
for (const row of rows) {
|
|
10191
|
+
const rowId = readRowId(row, resourceIdKey);
|
|
10192
|
+
const tagIds = row[field] ?? [];
|
|
10193
|
+
if (!rowId || !Array.isArray(tagIds) || !tagIds.every((tagId) => typeof tagId === "string")) {
|
|
10194
|
+
toast.error("Tag assignment requires a record id and an array of tag ids");
|
|
10195
|
+
return;
|
|
10196
|
+
}
|
|
10197
|
+
entities.push({
|
|
10198
|
+
id: rowId,
|
|
10199
|
+
tagIds: [...tagIds]
|
|
10200
|
+
});
|
|
10201
|
+
}
|
|
10202
|
+
setTagAssignment({
|
|
10203
|
+
resourceId: id,
|
|
10204
|
+
field,
|
|
10205
|
+
title: dialogTitle,
|
|
10206
|
+
entities
|
|
10207
|
+
});
|
|
10208
|
+
}, [
|
|
10209
|
+
id,
|
|
10210
|
+
canAssignTags,
|
|
10211
|
+
resourceIdKey
|
|
10212
|
+
]);
|
|
10162
10213
|
const actionRegistryVersion = useCrudActionsVersion();
|
|
10163
10214
|
const unifiedActions = React.useMemo(() => {
|
|
10164
10215
|
if (isUnifiedActionsConfig(actionItems)) return actionItems;
|
|
@@ -10168,8 +10219,28 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10168
10219
|
const ownerRowConfig = unifiedActions.row;
|
|
10169
10220
|
const ownerBatchConfig = unifiedActions.batch ?? tableConfig?.batchActions;
|
|
10170
10221
|
const ownerToolbarActions = React.useMemo(() => resolveOwnerToolbarActions(ownerToolbarConfig), [ownerToolbarConfig]);
|
|
10171
|
-
const ownerRowActions = React.useMemo(() => resolveOwnerRowActions(ownerRowConfig),
|
|
10172
|
-
|
|
10222
|
+
const ownerRowActions = React.useMemo(() => [...resolveOwnerRowActions(ownerRowConfig), ...canAssignTags ? tagFields.map(({ field, actionId, label }) => ({
|
|
10223
|
+
type: "custom",
|
|
10224
|
+
id: actionId,
|
|
10225
|
+
label,
|
|
10226
|
+
onClick: (row) => openTagAssignment(field, label, [row])
|
|
10227
|
+
})) : []], [
|
|
10228
|
+
ownerRowConfig,
|
|
10229
|
+
canAssignTags,
|
|
10230
|
+
tagFields,
|
|
10231
|
+
openTagAssignment
|
|
10232
|
+
]);
|
|
10233
|
+
const ownerBatchActions = React.useMemo(() => [...resolveOwnerBatchActions(ownerBatchConfig), ...canAssignTags ? tagFields.map(({ field, actionId, label }) => ({
|
|
10234
|
+
type: "custom",
|
|
10235
|
+
id: actionId,
|
|
10236
|
+
label,
|
|
10237
|
+
onClick: (rows) => openTagAssignment(field, label, rows)
|
|
10238
|
+
})) : []], [
|
|
10239
|
+
ownerBatchConfig,
|
|
10240
|
+
canAssignTags,
|
|
10241
|
+
tagFields,
|
|
10242
|
+
openTagAssignment
|
|
10243
|
+
]);
|
|
10173
10244
|
const registryToolbarActions = React.useMemo(() => crudActions.resolve(id, "toolbar", ownerToolbarActions), [
|
|
10174
10245
|
id,
|
|
10175
10246
|
ownerToolbarActions,
|
|
@@ -10359,7 +10430,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10359
10430
|
}
|
|
10360
10431
|
}
|
|
10361
10432
|
for (const [field, config] of Object.entries(resolvedFields)) {
|
|
10362
|
-
if (!config.filter || config.filter.variant !== "tags" || formattedOverrides[field]?.cell) continue;
|
|
10433
|
+
if (!config.tags && (!config.filter || config.filter.variant !== "tags") || formattedOverrides[field]?.cell) continue;
|
|
10363
10434
|
formattedOverrides[field] = {
|
|
10364
10435
|
...formattedOverrides[field],
|
|
10365
10436
|
enableSorting: formattedOverrides[field]?.enableSorting ?? false,
|
|
@@ -10609,6 +10680,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
10609
10680
|
...onExport ? { exportTableRef } : {}
|
|
10610
10681
|
}),
|
|
10611
10682
|
rowActionDialog,
|
|
10683
|
+
tagAssignmentVisible && tagAssignment && /* @__PURE__ */ jsx(TagAssignment, {
|
|
10684
|
+
...tagAssignment,
|
|
10685
|
+
open: true,
|
|
10686
|
+
onOpenChange: (open) => {
|
|
10687
|
+
if (!open) setTagAssignment(null);
|
|
10688
|
+
},
|
|
10689
|
+
onSaved: resource.handlers.refresh
|
|
10690
|
+
}),
|
|
10612
10691
|
/* @__PURE__ */ jsx(CrudFormModal, {
|
|
10613
10692
|
open: resource.modal.createOpen || resource.modal.editOpen,
|
|
10614
10693
|
onOpenChange: (open) => !open && resource.handlers.closeModals(),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordrhyme/auto-crud",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.18.0",
|
|
5
5
|
"description": "Schema-first CRUD components with auto-generated tables and forms",
|
|
6
6
|
"author": "wordrhyme",
|
|
7
7
|
"license": "MIT",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"tailwind-merge": "^3.5.0",
|
|
73
73
|
"vaul": "^1.1.2",
|
|
74
74
|
"@wordrhyme/formily-shadcn": "2.1.3",
|
|
75
|
-
"@wordrhyme/shadcn": "2.
|
|
76
|
-
"@wordrhyme/shadcn
|
|
75
|
+
"@wordrhyme/shadcn-ui": "2.1.3",
|
|
76
|
+
"@wordrhyme/shadcn": "2.0.2"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@storybook/react": "^8.6.18",
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
"@internal/eslint-config": "0.3.0",
|
|
98
98
|
"@internal/prettier-config": "0.0.1",
|
|
99
99
|
"@internal/tsconfig": "0.1.0",
|
|
100
|
-
"@internal/
|
|
101
|
-
"@internal/
|
|
100
|
+
"@internal/vitest-config": "0.1.0",
|
|
101
|
+
"@internal/tsdown-config": "0.1.0"
|
|
102
102
|
},
|
|
103
103
|
"prettier": "@internal/prettier-config",
|
|
104
104
|
"scripts": {
|