@asteby/metacore-runtime-react 13.5.2 → 13.7.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/CHANGELOG.md +37 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +6 -0
- package/dist/dynamic-form-schema.d.ts +10 -0
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +21 -0
- package/dist/dynamic-form.d.ts +1 -0
- package/dist/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +7 -0
- package/dist/dynamic-relation-helpers.d.ts +1 -1
- package/dist/dynamic-relation-helpers.d.ts.map +1 -1
- package/dist/dynamic-relation-helpers.js +17 -2
- package/dist/dynamic-relation.d.ts +8 -0
- package/dist/dynamic-relation.d.ts.map +1 -1
- package/dist/dynamic-relation.js +26 -12
- package/dist/dynamic-relations.d.ts +51 -0
- package/dist/dynamic-relations.d.ts.map +1 -0
- package/dist/dynamic-relations.js +76 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/types.d.ts +57 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/upload-field.d.ts +15 -0
- package/dist/upload-field.d.ts.map +1 -0
- package/dist/upload-field.js +109 -0
- package/package.json +4 -4
- package/src/__tests__/dynamic-relation.test.ts +28 -0
- package/src/__tests__/dynamic-relations.test.ts +60 -0
- package/src/__tests__/upload-field.test.ts +74 -0
- package/src/action-modal-dispatcher.tsx +6 -0
- package/src/dynamic-form-schema.ts +27 -0
- package/src/dynamic-form.tsx +7 -0
- package/src/dynamic-relation-helpers.ts +15 -1
- package/src/dynamic-relation.tsx +35 -10
- package/src/dynamic-relations.tsx +160 -0
- package/src/dynamic-table.tsx +61 -1
- package/src/index.ts +6 -0
- package/src/types.ts +58 -0
- package/src/upload-field.tsx +168 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 13.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 33165d2: feat(dynamic-table): card-per-row layout on mobile. On phones the multi-column data table forced a wide horizontal scroll; below the `sm` breakpoint the table is now replaced by a stacked card list (one card per row, columns shown as label : value pairs, row actions pinned at the bottom). Desktop keeps the classic table.
|
|
8
|
+
|
|
9
|
+
## 13.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- ee91499: feat(runtime-react): metadata-driven DynamicRelations, DynamicRelation scope filters, and an upload field widget
|
|
14
|
+
|
|
15
|
+
Three additive primitives for generic detail pages and file-bearing actions
|
|
16
|
+
(kernel >= v0.41.0):
|
|
17
|
+
- **`DynamicRelations`** — a metadata-driven panel list. Given a parent record
|
|
18
|
+
and `TableMetadata.relations[]` (the new `RelationMeta[]` the kernel serves),
|
|
19
|
+
it renders one `DynamicRelation` panel per relation, merging each relation's
|
|
20
|
+
static `scope` (polymorphic discriminators like `{ owner_model: "Customer" }`)
|
|
21
|
+
plus `{ <foreign_key>: parentId }` into the panel's `filters`. This is what a
|
|
22
|
+
generic detail page renders to show "a Customer's vehicles, addresses,
|
|
23
|
+
attachments".
|
|
24
|
+
- **`DynamicRelation.filters`** — new optional `filters?: Record<string,string>`
|
|
25
|
+
prop so a relation can be scoped by MORE than one column (the polymorphic
|
|
26
|
+
case: `foreign_key=owner_id` AND `owner_model=Customer`). Each entry threads
|
|
27
|
+
into the child list query as an additional `f_<col>=eq:<val>` param alongside
|
|
28
|
+
the foreign-key filter, is hidden from the rendered child columns, and is
|
|
29
|
+
folded into create/attach payloads so new children carry the scope.
|
|
30
|
+
- **`upload` field widget** — `type:"upload"` / `widget:"upload"` action fields
|
|
31
|
+
now render a themed file picker (semantic tokens) that POSTs the file to the
|
|
32
|
+
host upload endpoint as multipart and stores the returned file url/path as the
|
|
33
|
+
field value. Honors `accept` and `maxSize` (tolerates kernel snake_case
|
|
34
|
+
`max_size`/`storage_path`). Wired into BOTH the standalone `DynamicForm`
|
|
35
|
+
renderer and the `ActionModalDispatcher` renderer so they stay in sync.
|
|
36
|
+
|
|
37
|
+
All purely additive — zero behavioural change for existing relations, forms, and
|
|
38
|
+
action modals.
|
|
39
|
+
|
|
3
40
|
## 13.5.2
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AA+CA,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAExB,MAAM,sBAAsB,CAAA;AAE7B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAA;AAEhD,wBAAgB,qBAAqB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,GACZ,EAAE,gBAAgB,kDAiDlB"}
|
|
@@ -17,6 +17,7 @@ import { DynamicIcon } from './dynamic-icon';
|
|
|
17
17
|
import { DynamicLineItems } from './dynamic-line-items';
|
|
18
18
|
import { DynamicSelectField } from './dynamic-select-field';
|
|
19
19
|
import { DynamicDateField } from './dynamic-date-field';
|
|
20
|
+
import { UploadField } from './upload-field';
|
|
20
21
|
import { isLineItemsField, resolveWidget } from './dynamic-form-schema';
|
|
21
22
|
// Canonical registry lives in @asteby/metacore-sdk
|
|
22
23
|
import { getActionComponent, } from '@asteby/metacore-sdk';
|
|
@@ -155,6 +156,11 @@ function renderField(field, value, onChange) {
|
|
|
155
156
|
if (widget === 'dynamic_select') {
|
|
156
157
|
return _jsx(DynamicSelectField, { field: field, value: value, onChange: onChange });
|
|
157
158
|
}
|
|
159
|
+
// File upload → themed picker that POSTs the file to the host upload
|
|
160
|
+
// endpoint and stores the returned url/path. Kept in sync with DynamicForm.
|
|
161
|
+
if (widget === 'upload') {
|
|
162
|
+
return _jsx(UploadField, { field: field, value: value, onChange: onChange });
|
|
163
|
+
}
|
|
158
164
|
switch (widget) {
|
|
159
165
|
case 'textarea':
|
|
160
166
|
return _jsx(Textarea, { id: field.key, value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
|
|
@@ -54,4 +54,14 @@ export interface BalanceState {
|
|
|
54
54
|
*/
|
|
55
55
|
export declare function evaluateBalance(field: ActionFieldDef, rows: any[] | undefined): BalanceState | undefined;
|
|
56
56
|
export declare function resolveWidget(field: ActionFieldDef): string;
|
|
57
|
+
/**
|
|
58
|
+
* Normalizes an upload field's config, tolerating both the camelCase authored
|
|
59
|
+
* SDK shape and the snake_case the kernel serves (`max_size`, `storage_path`).
|
|
60
|
+
* Pure — shared by both field renderers and unit tests.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getUploadConfig(field: ActionFieldDef): {
|
|
63
|
+
accept?: string;
|
|
64
|
+
maxSize?: number;
|
|
65
|
+
storagePath?: string;
|
|
66
|
+
};
|
|
57
67
|
//# sourceMappingURL=dynamic-form-schema.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAA;AAiB9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,CAGrE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,cAAc,GACtB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAatG;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,YAAY,GAAG,SAAS,CAgB1B;AAqDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAA;AAiB9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,CAGrE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,cAAc,GACtB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAatG;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,YAAY,GAAG,SAAS,CAgB1B;AAqDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAgB3D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB,CAaA"}
|
|
@@ -199,6 +199,27 @@ export function resolveWidget(field) {
|
|
|
199
199
|
case 'boolean': return 'switch';
|
|
200
200
|
case 'number': return 'number';
|
|
201
201
|
case 'date': return 'date';
|
|
202
|
+
// File upload: POSTs to the host upload endpoint and stores the returned
|
|
203
|
+
// file url/path as the field value. Rendered by `UploadField`.
|
|
204
|
+
case 'upload': return 'upload';
|
|
202
205
|
default: return 'text';
|
|
203
206
|
}
|
|
204
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Normalizes an upload field's config, tolerating both the camelCase authored
|
|
210
|
+
* SDK shape and the snake_case the kernel serves (`max_size`, `storage_path`).
|
|
211
|
+
* Pure — shared by both field renderers and unit tests.
|
|
212
|
+
*/
|
|
213
|
+
export function getUploadConfig(field) {
|
|
214
|
+
const accept = field.accept;
|
|
215
|
+
const maxSizeRaw = field.maxSize ?? field.max_size;
|
|
216
|
+
const maxSize = typeof maxSizeRaw === 'number' && Number.isFinite(maxSizeRaw) && maxSizeRaw > 0
|
|
217
|
+
? maxSizeRaw
|
|
218
|
+
: undefined;
|
|
219
|
+
const storagePath = field.storagePath ?? field.storage_path;
|
|
220
|
+
return {
|
|
221
|
+
accept: accept || undefined,
|
|
222
|
+
maxSize,
|
|
223
|
+
storagePath: storagePath || undefined,
|
|
224
|
+
};
|
|
225
|
+
}
|
package/dist/dynamic-form.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { buildZodSchema, resolveWidget };
|
|
|
4
4
|
export { DynamicLineItems } from './dynamic-line-items';
|
|
5
5
|
export { DynamicSelectField } from './dynamic-select-field';
|
|
6
6
|
export { DynamicDateField } from './dynamic-date-field';
|
|
7
|
+
export { UploadField } from './upload-field';
|
|
7
8
|
export interface DynamicFormProps {
|
|
8
9
|
fields: ActionFieldDef[];
|
|
9
10
|
initialValues?: Record<string, any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAGhB,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAGhB,MAAM,uBAAuB,CAAA;AAO9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,2CAkGlB"}
|
package/dist/dynamic-form.js
CHANGED
|
@@ -9,10 +9,12 @@ import { useOptionsResolver } from './use-options-resolver';
|
|
|
9
9
|
import { DynamicLineItems } from './dynamic-line-items';
|
|
10
10
|
import { DynamicSelectField } from './dynamic-select-field';
|
|
11
11
|
import { DynamicDateField } from './dynamic-date-field';
|
|
12
|
+
import { UploadField } from './upload-field';
|
|
12
13
|
export { buildZodSchema, resolveWidget };
|
|
13
14
|
export { DynamicLineItems } from './dynamic-line-items';
|
|
14
15
|
export { DynamicSelectField } from './dynamic-select-field';
|
|
15
16
|
export { DynamicDateField } from './dynamic-date-field';
|
|
17
|
+
export { UploadField } from './upload-field';
|
|
16
18
|
export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel = 'Guardar', cancelLabel = 'Cancelar', disabled = false, }) {
|
|
17
19
|
const [values, setValues] = useState({});
|
|
18
20
|
const [errors, setErrors] = useState({});
|
|
@@ -91,6 +93,11 @@ function FieldRenderer({ field, value, onChange }) {
|
|
|
91
93
|
if (widget === 'dynamic_select') {
|
|
92
94
|
return _jsx(DynamicSelectField, { field: field, value: value, onChange: onChange });
|
|
93
95
|
}
|
|
96
|
+
// File upload → themed picker that POSTs to the host upload endpoint and
|
|
97
|
+
// stores the returned file url/path as the field value.
|
|
98
|
+
if (widget === 'upload') {
|
|
99
|
+
return _jsx(UploadField, { field: field, value: value, onChange: onChange });
|
|
100
|
+
}
|
|
94
101
|
// Ref-driven select: hook into useOptionsResolver so the canonical
|
|
95
102
|
// /api/options/<ref>?field=id endpoint feeds the dropdown. This is
|
|
96
103
|
// the path the kernel auto-derives for FK columns; legacy callers
|
|
@@ -14,7 +14,7 @@ export interface TargetRowLike {
|
|
|
14
14
|
* `f_<column>=eq:<value>` convention enforced by `query/params.go` in the
|
|
15
15
|
* kernel.
|
|
16
16
|
*/
|
|
17
|
-
export declare function buildRelationFilterParams(foreignKey: string, parentId: string | number): Record<string, string>;
|
|
17
|
+
export declare function buildRelationFilterParams(foreignKey: string, parentId: string | number, extraFilters?: Record<string, string> | null): Record<string, string>;
|
|
18
18
|
/**
|
|
19
19
|
* Builds the POST body for creating a child row. The foreign key is forced to
|
|
20
20
|
* `parentId` regardless of what the form returned — the inline form hides the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-relation-helpers.d.ts","sourceRoot":"","sources":["../src/dynamic-relation-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE9E,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,cAAc,CAAA;AAEhE,MAAM,WAAW,YAAY;IACzB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"dynamic-relation-helpers.d.ts","sourceRoot":"","sources":["../src/dynamic-relation-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE9E,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,cAAc,CAAA;AAEhE,MAAM,WAAW,YAAY;IACzB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAGrB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3D,UAAU,EAAE,MAAM,GACnB,cAAc,EAAE,CAelB;AAcD;;;;GAIG;AACH,wBAAgB,cAAc,CAC1B,GAAG,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,EAChD,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACnB,MAAM,CAKR;AAMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACnC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAcrB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACpC,SAAS,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,GAAG,SAAS,EACzD,aAAa,EAAE,MAAM,GACtB,MAAM,EAAE,CASV;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,GAAG,SAAS,EACzD,aAAa,EAAE,MAAM,GACtB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAU9B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC5B;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAYzC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC3B,GAAG,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,IAAI,GAAG,SAAS,GAC5D,MAAM,CAiBR"}
|
|
@@ -4,13 +4,28 @@
|
|
|
4
4
|
* `f_<column>=eq:<value>` convention enforced by `query/params.go` in the
|
|
5
5
|
* kernel.
|
|
6
6
|
*/
|
|
7
|
-
export function buildRelationFilterParams(foreignKey, parentId) {
|
|
7
|
+
export function buildRelationFilterParams(foreignKey, parentId, extraFilters) {
|
|
8
8
|
if (!foreignKey)
|
|
9
9
|
throw new Error('foreignKey requerido');
|
|
10
10
|
if (parentId === undefined || parentId === null || parentId === '') {
|
|
11
11
|
throw new Error('parentId requerido');
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
const params = {
|
|
14
|
+
[`f_${foreignKey}`]: `eq:${String(parentId)}`,
|
|
15
|
+
};
|
|
16
|
+
// Additional static-equality scope columns (polymorphic case: the FK plus
|
|
17
|
+
// e.g. owner_model=Customer). Each becomes its own `f_<col>=eq:<val>` param.
|
|
18
|
+
// The foreign-key entry above wins if a caller redundantly repeats it.
|
|
19
|
+
if (extraFilters) {
|
|
20
|
+
for (const [col, val] of Object.entries(extraFilters)) {
|
|
21
|
+
if (!col || col === foreignKey)
|
|
22
|
+
continue;
|
|
23
|
+
if (val === undefined || val === null)
|
|
24
|
+
continue;
|
|
25
|
+
params[`f_${col}`] = `eq:${String(val)}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return params;
|
|
14
29
|
}
|
|
15
30
|
/**
|
|
16
31
|
* Builds the POST body for creating a child row. The foreign key is forced to
|
|
@@ -17,6 +17,14 @@ export interface DynamicRelationStrings {
|
|
|
17
17
|
interface CommonProps {
|
|
18
18
|
/** id del registro padre. */
|
|
19
19
|
parentId: string | number;
|
|
20
|
+
/**
|
|
21
|
+
* Filtros estáticos extra (igualdad) aplicados ADEMÁS del foreign-key.
|
|
22
|
+
* Caso polimórfico: una tabla de hijos compartida (attachments,
|
|
23
|
+
* addresses) scopeada por `foreign_key=owner_id` Y `owner_model=Customer`.
|
|
24
|
+
* Cada entrada se thread-ea como `f_<col>=eq:<val>` junto al FK en la query
|
|
25
|
+
* de la lista hija. Aditivo: sin filters el comportamiento es idéntico.
|
|
26
|
+
*/
|
|
27
|
+
filters?: Record<string, string>;
|
|
20
28
|
/** Hidden columns; el FK siempre se oculta automáticamente. */
|
|
21
29
|
hiddenColumns?: string[];
|
|
22
30
|
/** Permisos visibles. Default true. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-relation.d.ts","sourceRoot":"","sources":["../src/dynamic-relation.tsx"],"names":[],"mappings":"AA0CA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,wBAAwB,EACxB,eAAe,EACf,cAAc,GACjB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD,UAAU,WAAW;IACjB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,6BAA8B,SAAQ,WAAW;IAC9D,IAAI,EAAE,aAAa,CAAA;IACnB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,WAAW;IAC/D,IAAI,EAAE,cAAc,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC1B,6BAA6B,GAC7B,8BAA8B,CAAA;AAEpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAK1D"}
|
|
1
|
+
{"version":3,"file":"dynamic-relation.d.ts","sourceRoot":"","sources":["../src/dynamic-relation.tsx"],"names":[],"mappings":"AA0CA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,wBAAwB,EACxB,eAAe,EACf,cAAc,GACjB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD,UAAU,WAAW;IACjB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,6BAA8B,SAAQ,WAAW;IAC9D,IAAI,EAAE,aAAa,CAAA;IACnB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,WAAW;IAC/D,IAAI,EAAE,cAAc,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC1B,6BAA6B,GAC7B,8BAA8B,CAAA;AAEpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAK1D"}
|
package/dist/dynamic-relation.js
CHANGED
|
@@ -33,7 +33,7 @@ export function DynamicRelation(props) {
|
|
|
33
33
|
}
|
|
34
34
|
return _jsx(OneToManyRelation, { ...props });
|
|
35
35
|
}
|
|
36
|
-
function OneToManyRelation({ kind, model, foreignKey, parentId, endpoint, hiddenColumns = [], canCreate = true, canDelete = true, canEdit = true, strings, className, onChange, }) {
|
|
36
|
+
function OneToManyRelation({ kind, model, foreignKey, parentId, filters, endpoint, hiddenColumns = [], canCreate = true, canDelete = true, canEdit = true, strings, className, onChange, }) {
|
|
37
37
|
const api = useApi();
|
|
38
38
|
const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache();
|
|
39
39
|
const cachedMeta = getMetadata(model);
|
|
@@ -46,10 +46,14 @@ function OneToManyRelation({ kind, model, foreignKey, parentId, endpoint, hidden
|
|
|
46
46
|
const [rowToDelete, setRowToDelete] = useState(null);
|
|
47
47
|
const [submitting, setSubmitting] = useState(false);
|
|
48
48
|
const dataEndpoint = endpoint || `/data/${model}`;
|
|
49
|
+
// Stable dependency key for the filters object (callers usually pass a fresh
|
|
50
|
+
// literal each render). Keeps fetchAll from re-firing on identity churn while
|
|
51
|
+
// still reacting to real scope changes.
|
|
52
|
+
const filtersKey = useMemo(() => (filters ? JSON.stringify(filters) : ''), [filters]);
|
|
49
53
|
const fetchAll = useCallback(async () => {
|
|
50
54
|
setLoading(true);
|
|
51
55
|
try {
|
|
52
|
-
const params = buildRelationFilterParams(foreignKey, parentId);
|
|
56
|
+
const params = buildRelationFilterParams(foreignKey, parentId, filters);
|
|
53
57
|
const [metaRes, dataRes] = await Promise.all([
|
|
54
58
|
metadata ? Promise.resolve(null) : api.get(`/metadata/table/${model}`),
|
|
55
59
|
api.get(dataEndpoint, { params }),
|
|
@@ -69,15 +73,18 @@ function OneToManyRelation({ kind, model, foreignKey, parentId, endpoint, hidden
|
|
|
69
73
|
finally {
|
|
70
74
|
setLoading(false);
|
|
71
75
|
}
|
|
72
|
-
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
}, [api, dataEndpoint, foreignKey, parentId, filtersKey, metadata, model, cacheMetadata]);
|
|
73
78
|
useEffect(() => { fetchAll(); }, [fetchAll]);
|
|
74
79
|
const formFields = useMemo(() => deriveRelationFormFields(metadata, foreignKey), [metadata, foreignKey]);
|
|
75
80
|
const visibleColumns = useMemo(() => {
|
|
76
81
|
if (!metadata?.columns)
|
|
77
82
|
return [];
|
|
78
|
-
|
|
83
|
+
// Hide the FK and every scope column — they're fixed for this parent and
|
|
84
|
+
// would just render the same value on every row.
|
|
85
|
+
const hidden = new Set([foreignKey, ...Object.keys(filters || {}), ...hiddenColumns]);
|
|
79
86
|
return metadata.columns.filter(c => !hidden.has(c.key) && !c.hidden);
|
|
80
|
-
}, [metadata, foreignKey, hiddenColumns]);
|
|
87
|
+
}, [metadata, foreignKey, filtersKey, hiddenColumns]);
|
|
81
88
|
const handleSubmit = useCallback(async (values) => {
|
|
82
89
|
setSubmitting(true);
|
|
83
90
|
try {
|
|
@@ -87,7 +94,10 @@ function OneToManyRelation({ kind, model, foreignKey, parentId, endpoint, hidden
|
|
|
87
94
|
throw new Error('update failed');
|
|
88
95
|
}
|
|
89
96
|
else {
|
|
90
|
-
|
|
97
|
+
// Scope columns (polymorphic discriminators like owner_model)
|
|
98
|
+
// are fixed for this relation, so a newly created child must
|
|
99
|
+
// carry them too — otherwise it would not match the list filter.
|
|
100
|
+
const payload = { ...(filters || {}), ...buildCreatePayload(foreignKey, parentId, values) };
|
|
91
101
|
const res = await api.post(dataEndpoint, payload);
|
|
92
102
|
if (!res.data?.success)
|
|
93
103
|
throw new Error('create failed');
|
|
@@ -103,7 +113,8 @@ function OneToManyRelation({ kind, model, foreignKey, parentId, endpoint, hidden
|
|
|
103
113
|
finally {
|
|
104
114
|
setSubmitting(false);
|
|
105
115
|
}
|
|
106
|
-
|
|
116
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
117
|
+
}, [api, dataEndpoint, editingRow, fetchAll, foreignKey, filtersKey, onChange, parentId]);
|
|
107
118
|
const handleDelete = useCallback(async () => {
|
|
108
119
|
if (!rowToDelete)
|
|
109
120
|
return;
|
|
@@ -135,7 +146,7 @@ function formatCell(value) {
|
|
|
135
146
|
return JSON.stringify(value);
|
|
136
147
|
return String(value);
|
|
137
148
|
}
|
|
138
|
-
function ManyToManyRelation({ kind, through, references, foreignKey, referencesKey, parentId, pivotEndpoint, referencesEndpoint, displayKey, canCreate = true, canDelete = true, strings, className, onChange, }) {
|
|
149
|
+
function ManyToManyRelation({ kind, through, references, foreignKey, referencesKey, parentId, filters, pivotEndpoint, referencesEndpoint, displayKey, canCreate = true, canDelete = true, strings, className, onChange, }) {
|
|
139
150
|
const api = useApi();
|
|
140
151
|
const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache();
|
|
141
152
|
const labels = { ...DEFAULT_STRINGS, ...(strings || {}) };
|
|
@@ -162,10 +173,11 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
162
173
|
ref: useResolver ? references : undefined,
|
|
163
174
|
enabled: useResolver,
|
|
164
175
|
});
|
|
176
|
+
const filtersKey = useMemo(() => (filters ? JSON.stringify(filters) : ''), [filters]);
|
|
165
177
|
const fetchPivotAndMeta = useCallback(async () => {
|
|
166
178
|
setLoading(true);
|
|
167
179
|
try {
|
|
168
|
-
const params = buildRelationFilterParams(foreignKey, parentId);
|
|
180
|
+
const params = buildRelationFilterParams(foreignKey, parentId, filters);
|
|
169
181
|
const tasks = [
|
|
170
182
|
api.get(pivotPath, { params }),
|
|
171
183
|
];
|
|
@@ -200,7 +212,8 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
200
212
|
finally {
|
|
201
213
|
setLoading(false);
|
|
202
214
|
}
|
|
203
|
-
|
|
215
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
216
|
+
}, [api, pivotPath, foreignKey, parentId, filtersKey, references, targetMeta, cacheMetadata, useResolver, legacyTargetPath]);
|
|
204
217
|
useEffect(() => { fetchPivotAndMeta(); }, [fetchPivotAndMeta]);
|
|
205
218
|
const options = useMemo(() => {
|
|
206
219
|
if (useResolver) {
|
|
@@ -231,7 +244,7 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
231
244
|
setSyncing(true);
|
|
232
245
|
try {
|
|
233
246
|
for (const targetId of toAdd) {
|
|
234
|
-
const payload = buildPivotAttachPayload(foreignKey, parentId, refKey, targetId);
|
|
247
|
+
const payload = buildPivotAttachPayload(foreignKey, parentId, refKey, targetId, filters || undefined);
|
|
235
248
|
const res = await api.post(pivotPath, payload);
|
|
236
249
|
if (!res.data?.success)
|
|
237
250
|
throw new Error('attach failed');
|
|
@@ -258,6 +271,7 @@ function ManyToManyRelation({ kind, through, references, foreignKey, referencesK
|
|
|
258
271
|
finally {
|
|
259
272
|
setSyncing(false);
|
|
260
273
|
}
|
|
261
|
-
|
|
274
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
275
|
+
}, [api, canCreate, canDelete, fetchPivotAndMeta, useResolver, resolved, foreignKey, filtersKey, onChange, parentId, pivotIndex, pivotPath, refKey, selectedIds, syncing]);
|
|
262
276
|
return (_jsxs("div", { className: className, "data-relation-kind": kind, "data-relation-through": through, "data-relation-references": references, children: [labels.title && (_jsx("div", { className: "pb-3", children: _jsx("h3", { className: "text-sm font-medium", children: labels.title }) })), (loading || (useResolver && resolved.loading)) ? (_jsx(Skeleton, { className: "h-10 w-full" })) : options.length === 0 ? (_jsx("div", { className: "text-center text-sm text-muted-foreground py-8 border rounded-md bg-muted/30", children: labels.emptyState })) : (_jsx(MultiSelect, { options: options, selected: selectedIds, onChange: handleChange, placeholder: labels.selectPlaceholder, searchPlaceholder: labels.selectSearchPlaceholder, emptyMessage: labels.selectEmpty }))] }));
|
|
263
277
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type DynamicRelationStrings } from './dynamic-relation';
|
|
2
|
+
import type { RelationMeta } from './types';
|
|
3
|
+
export interface DynamicRelationsProps {
|
|
4
|
+
/**
|
|
5
|
+
* The parent record. Its `id` (or `parentIdKey`) seeds every child list's
|
|
6
|
+
* foreign-key filter. Null/undefined → renders nothing (loading guard).
|
|
7
|
+
*/
|
|
8
|
+
record: {
|
|
9
|
+
id?: string | number;
|
|
10
|
+
[k: string]: unknown;
|
|
11
|
+
} | null | undefined;
|
|
12
|
+
/** The relations to render — typically `metadata.relations`. */
|
|
13
|
+
relations: RelationMeta[] | null | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Which field of `record` holds the parent id. Default `'id'`. Lets a host
|
|
16
|
+
* key relations off a non-`id` primary key.
|
|
17
|
+
*/
|
|
18
|
+
parentIdKey?: string;
|
|
19
|
+
/** Wrapper className for the whole stack. */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Per-panel wrapper className. */
|
|
22
|
+
panelClassName?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Permisos propagados a cada panel. Default true. A host can lock the whole
|
|
25
|
+
* detail page read-only by passing canCreate/canDelete/canEdit = false.
|
|
26
|
+
*/
|
|
27
|
+
canCreate?: boolean;
|
|
28
|
+
canDelete?: boolean;
|
|
29
|
+
canEdit?: boolean;
|
|
30
|
+
/** Translatable strings forwarded to each DynamicRelation. */
|
|
31
|
+
strings?: Partial<DynamicRelationStrings>;
|
|
32
|
+
/** Bubble up when any panel's data changes (create/delete/attach/detach). */
|
|
33
|
+
onChange?: (relation: RelationMeta) => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Normalizes the parent id off the record, tolerating a custom `parentIdKey`.
|
|
37
|
+
* Returns `undefined` when unusable so callers can guard rendering.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveParentId(record: {
|
|
40
|
+
[k: string]: unknown;
|
|
41
|
+
} | null | undefined, parentIdKey?: string): string | number | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Merges a relation's static `scope` with its foreign-key entry into the flat
|
|
44
|
+
* `filters` map `<DynamicRelation>` expects. The FK is included so a panel that
|
|
45
|
+
* only consumes `filters` (rather than the dedicated `foreignKey` prop) stays
|
|
46
|
+
* correctly scoped; `<DynamicRelation>` already de-dups the FK so passing it in
|
|
47
|
+
* both places is safe.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildRelationFilters(relation: Pick<RelationMeta, 'foreign_key' | 'scope'>, parentId: string | number): Record<string, string>;
|
|
50
|
+
export declare function DynamicRelations({ record, relations, parentIdKey, className, panelClassName, canCreate, canDelete, canEdit, strings, onChange, }: DynamicRelationsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
51
|
+
//# sourceMappingURL=dynamic-relations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-relations.d.ts","sourceRoot":"","sources":["../src/dynamic-relations.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,MAAM,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAA;IACzE,gEAAgE;IAChE,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAA;CAC9C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,MAAM,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACnD,WAAW,SAAO,GACnB,MAAM,GAAG,MAAM,GAAG,SAAS,CAM7B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,OAAO,CAAC,EACrD,QAAQ,EAAE,MAAM,GAAG,MAAM,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB;AAOD,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,SAAS,EACT,WAAkB,EAClB,SAAS,EACT,cAAc,EACd,SAAgB,EAChB,SAAgB,EAChB,OAAc,EACd,OAAO,EACP,QAAQ,GACX,EAAE,qBAAqB,kDA4DvB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// DynamicRelations — metadata-driven panel list. Given a parent record and the
|
|
3
|
+
// `TableMetadata.relations[]` the kernel serves (>= v0.41.0), it renders one
|
|
4
|
+
// `<DynamicRelation>` panel per relation. This is what a generic detail page
|
|
5
|
+
// renders to surface "a Customer's vehicles, addresses, attachments" without
|
|
6
|
+
// hand-wiring each child list.
|
|
7
|
+
//
|
|
8
|
+
// For every RelationMeta it:
|
|
9
|
+
// - maps `kind` straight through (one_to_many | many_to_many),
|
|
10
|
+
// - uses `through` as the child/pivot model and `foreign_key` as the FK,
|
|
11
|
+
// - merges the relation's static `scope` (polymorphic discriminators, e.g.
|
|
12
|
+
// { owner_model: "Customer" }) into the panel's `filters` so the child list
|
|
13
|
+
// is scoped by the FK AND every scope column.
|
|
14
|
+
import { useMemo } from 'react';
|
|
15
|
+
import { DynamicRelation } from './dynamic-relation';
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes the parent id off the record, tolerating a custom `parentIdKey`.
|
|
18
|
+
* Returns `undefined` when unusable so callers can guard rendering.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveParentId(record, parentIdKey = 'id') {
|
|
21
|
+
if (!record)
|
|
22
|
+
return undefined;
|
|
23
|
+
const raw = record[parentIdKey];
|
|
24
|
+
if (raw === undefined || raw === null || raw === '')
|
|
25
|
+
return undefined;
|
|
26
|
+
if (typeof raw === 'number' || typeof raw === 'string')
|
|
27
|
+
return raw;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Merges a relation's static `scope` with its foreign-key entry into the flat
|
|
32
|
+
* `filters` map `<DynamicRelation>` expects. The FK is included so a panel that
|
|
33
|
+
* only consumes `filters` (rather than the dedicated `foreignKey` prop) stays
|
|
34
|
+
* correctly scoped; `<DynamicRelation>` already de-dups the FK so passing it in
|
|
35
|
+
* both places is safe.
|
|
36
|
+
*/
|
|
37
|
+
export function buildRelationFilters(relation, parentId) {
|
|
38
|
+
const out = {};
|
|
39
|
+
if (relation.scope) {
|
|
40
|
+
for (const [k, v] of Object.entries(relation.scope)) {
|
|
41
|
+
if (!k || v === undefined || v === null)
|
|
42
|
+
continue;
|
|
43
|
+
out[k] = String(v);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (relation.foreign_key)
|
|
47
|
+
out[relation.foreign_key] = String(parentId);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Stable React key for a relation panel. */
|
|
51
|
+
function relationKey(rel, idx) {
|
|
52
|
+
return rel.name || `${rel.through}-${rel.foreign_key}-${idx}`;
|
|
53
|
+
}
|
|
54
|
+
export function DynamicRelations({ record, relations, parentIdKey = 'id', className, panelClassName, canCreate = true, canDelete = true, canEdit = true, strings, onChange, }) {
|
|
55
|
+
const parentId = useMemo(() => resolveParentId(record, parentIdKey), [record, parentIdKey]);
|
|
56
|
+
if (parentId === undefined || !relations || relations.length === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return (_jsx("div", { className: className, "data-dynamic-relations": "", children: relations.map((rel, idx) => {
|
|
60
|
+
const filters = buildRelationFilters(rel, parentId);
|
|
61
|
+
const panelStrings = {
|
|
62
|
+
...(strings || {}),
|
|
63
|
+
...(rel.label ? { title: rel.label } : {}),
|
|
64
|
+
};
|
|
65
|
+
if (rel.kind === 'many_to_many') {
|
|
66
|
+
return (_jsx(DynamicRelation, { kind: "many_to_many", through: rel.through,
|
|
67
|
+
// The pivot's reference table is unknown from
|
|
68
|
+
// RelationMeta alone; default to the through model so
|
|
69
|
+
// the panel degrades to the pivot list. Hosts with a
|
|
70
|
+
// declared `references` should render DynamicRelation
|
|
71
|
+
// directly. (one_to_many is the common detail-page case.)
|
|
72
|
+
references: rel.through, foreignKey: rel.foreign_key, parentId: parentId, filters: filters, className: panelClassName, canCreate: canCreate, canDelete: canDelete, strings: panelStrings, onChange: onChange ? () => onChange(rel) : undefined }, relationKey(rel, idx)));
|
|
73
|
+
}
|
|
74
|
+
return (_jsx(DynamicRelation, { kind: "one_to_many", model: rel.through, foreignKey: rel.foreign_key, parentId: parentId, filters: filters, className: panelClassName, canCreate: canCreate, canDelete: canDelete, canEdit: canEdit, strings: panelStrings, onChange: onChange ? () => onChange(rel) : undefined }, relationKey(rel, idx)));
|
|
75
|
+
}) }));
|
|
76
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CAixBnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -574,11 +574,20 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
574
574
|
if (!metadata) {
|
|
575
575
|
return _jsx("div", { className: "text-center text-muted-foreground py-8", children: "Error al cargar la configuraci\u00F3n de la tabla." });
|
|
576
576
|
}
|
|
577
|
-
return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [metadata.canExport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), metadata.canImport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsx("div", { className: 'flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { noWrapper: true, className: "min-w-max w-full", children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
|
|
577
|
+
return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [metadata.canExport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), metadata.canImport && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsx("div", { className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { noWrapper: true, className: "min-w-max w-full", children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
|
|
578
578
|
const isActionsColumn = header.id === 'actions';
|
|
579
579
|
return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
580
580
|
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', children: row.getVisibleCells().map((cell) => {
|
|
581
581
|
const isActionsColumn = cell.column.id === 'actions';
|
|
582
582
|
return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
583
|
-
}) }, row.id)))) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })] }) }) })) })] }) }), _jsx("div", { className: '
|
|
583
|
+
}) }, row.id)))) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })] }) }) })) })] }) }), _jsx("div", { className: 'flex flex-1 min-h-0 flex-col gap-2 overflow-y-auto sm:hidden', children: loadingData && data.length === 0 ? (Array.from({ length: 5 }).map((_, i) => (_jsxs("div", { className: 'rounded-lg border bg-card p-3', children: [_jsx(Skeleton, { className: 'h-4 w-24' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-40' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-32' })] }, i)))) : table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => {
|
|
584
|
+
const cells = row.getVisibleCells();
|
|
585
|
+
const actionsCell = cells.find((c) => c.column.id === 'actions');
|
|
586
|
+
const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
|
|
587
|
+
return (_jsxs("div", { "data-state": row.getIsSelected() && 'selected', className: 'flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', children: [dataCells.map((cell) => {
|
|
588
|
+
const header = cell.column.columnDef.header;
|
|
589
|
+
const label = typeof header === 'string' ? header : cell.column.id;
|
|
590
|
+
return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 break-words text-right font-medium', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
|
|
591
|
+
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
592
|
+
})) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })) }), _jsx("div", { className: 'shrink-0 pt-4', children: _jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions }) })] }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "\u00BFEst\u00E1 absolutamente seguro?" }), _jsx(AlertDialogDescription, { children: "Esta acci\u00F3n no se puede deshacer. Esto eliminar\u00E1 permanentemente el registro seleccionado de nuestros servidores." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: isDeleting, children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: isDeleting, children: isDeleting ? 'Eliminando...' : 'Eliminar' })] })] }) }), _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), _jsx(DynamicRecordDialog, { open: recordDialog.open, onOpenChange: (open) => setRecordDialog((prev) => ({ ...prev, open })), mode: recordDialog.mode, model: model, recordId: recordDialog.recordId, endpoint: endpoint, onSaved: handleRefresh }), metadata.canExport && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), metadata.canImport && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), actionModal.action && (_jsx(ActionModalDispatcher, { open: actionModal.open, onOpenChange: (open) => setActionModal((prev) => ({ ...prev, open })), action: actionModal.action, model: model, record: actionModal.record, endpoint: endpoint, onSuccess: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
|
|
584
593
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { ExportDialog } from './dialogs/export';
|
|
|
25
25
|
export { ImportDialog } from './dialogs/import';
|
|
26
26
|
export { DynamicCRUDPage, type DynamicCRUDPageProps, type DynamicCRUDPageStrings, type DynamicCRUDPageClasses, } from './dynamic-crud-page';
|
|
27
27
|
export { DynamicRelation, type DynamicRelationProps, type DynamicRelationStrings, type DynamicRelationKind, buildRelationFilterParams, buildCreatePayload, deriveRelationFormFields, relationRowKey, } from './dynamic-relation';
|
|
28
|
+
export { DynamicRelations, resolveParentId, buildRelationFilters, type DynamicRelationsProps, } from './dynamic-relations';
|
|
28
29
|
export { registerModelExtension, getModelExtension, clearModelExtensions, type ModelExtension, type ModelExtensionProps, } from './model-extension-registry';
|
|
29
30
|
export { isColumnVisibleInTable, getSearchableColumnKeys, } from './column-visibility';
|
|
30
31
|
export { useOptionsResolver, projectOption, type ResolvedOption, type OptionsMeta, type UseOptionsResolverArgs, type UseOptionsResolverResult, } from './use-options-resolver';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export { ExportDialog } from './dialogs/export';
|
|
|
28
28
|
export { ImportDialog } from './dialogs/import';
|
|
29
29
|
export { DynamicCRUDPage, } from './dynamic-crud-page';
|
|
30
30
|
export { DynamicRelation, buildRelationFilterParams, buildCreatePayload, deriveRelationFormFields, relationRowKey, } from './dynamic-relation';
|
|
31
|
+
export { DynamicRelations, resolveParentId, buildRelationFilters, } from './dynamic-relations';
|
|
31
32
|
export { registerModelExtension, getModelExtension, clearModelExtensions, } from './model-extension-registry';
|
|
32
33
|
export { isColumnVisibleInTable, getSearchableColumnKeys, } from './column-visibility';
|
|
33
34
|
export { useOptionsResolver, projectOption, } from './use-options-resolver';
|