@asteby/metacore-runtime-react 13.5.1 → 13.6.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/action-modal-dispatcher.d.ts.map +1 -1
  3. package/dist/action-modal-dispatcher.js +6 -0
  4. package/dist/dynamic-columns.d.ts +13 -0
  5. package/dist/dynamic-columns.d.ts.map +1 -1
  6. package/dist/dynamic-columns.js +22 -0
  7. package/dist/dynamic-form-schema.d.ts +10 -0
  8. package/dist/dynamic-form-schema.d.ts.map +1 -1
  9. package/dist/dynamic-form-schema.js +21 -0
  10. package/dist/dynamic-form.d.ts +1 -0
  11. package/dist/dynamic-form.d.ts.map +1 -1
  12. package/dist/dynamic-form.js +7 -0
  13. package/dist/dynamic-relation-helpers.d.ts +1 -1
  14. package/dist/dynamic-relation-helpers.d.ts.map +1 -1
  15. package/dist/dynamic-relation-helpers.js +17 -2
  16. package/dist/dynamic-relation.d.ts +8 -0
  17. package/dist/dynamic-relation.d.ts.map +1 -1
  18. package/dist/dynamic-relation.js +26 -12
  19. package/dist/dynamic-relations.d.ts +51 -0
  20. package/dist/dynamic-relations.d.ts.map +1 -0
  21. package/dist/dynamic-relations.js +76 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/types.d.ts +57 -1
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/upload-field.d.ts +15 -0
  28. package/dist/upload-field.d.ts.map +1 -0
  29. package/dist/upload-field.js +109 -0
  30. package/package.json +1 -1
  31. package/src/__tests__/action-visibility-by-state.test.ts +51 -0
  32. package/src/__tests__/dynamic-relation.test.ts +28 -0
  33. package/src/__tests__/dynamic-relations.test.ts +60 -0
  34. package/src/__tests__/upload-field.test.ts +74 -0
  35. package/src/action-modal-dispatcher.tsx +6 -0
  36. package/src/dynamic-columns.tsx +21 -0
  37. package/src/dynamic-form-schema.ts +27 -0
  38. package/src/dynamic-form.tsx +7 -0
  39. package/src/dynamic-relation-helpers.ts +15 -1
  40. package/src/dynamic-relation.tsx +35 -10
  41. package/src/dynamic-relations.tsx +160 -0
  42. package/src/index.ts +6 -0
  43. package/src/types.ts +58 -0
  44. package/src/upload-field.tsx +168 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 13.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ee91499: feat(runtime-react): metadata-driven DynamicRelations, DynamicRelation scope filters, and an upload field widget
8
+
9
+ Three additive primitives for generic detail pages and file-bearing actions
10
+ (kernel >= v0.41.0):
11
+ - **`DynamicRelations`** — a metadata-driven panel list. Given a parent record
12
+ and `TableMetadata.relations[]` (the new `RelationMeta[]` the kernel serves),
13
+ it renders one `DynamicRelation` panel per relation, merging each relation's
14
+ static `scope` (polymorphic discriminators like `{ owner_model: "Customer" }`)
15
+ plus `{ <foreign_key>: parentId }` into the panel's `filters`. This is what a
16
+ generic detail page renders to show "a Customer's vehicles, addresses,
17
+ attachments".
18
+ - **`DynamicRelation.filters`** — new optional `filters?: Record<string,string>`
19
+ prop so a relation can be scoped by MORE than one column (the polymorphic
20
+ case: `foreign_key=owner_id` AND `owner_model=Customer`). Each entry threads
21
+ into the child list query as an additional `f_<col>=eq:<val>` param alongside
22
+ the foreign-key filter, is hidden from the rendered child columns, and is
23
+ folded into create/attach payloads so new children carry the scope.
24
+ - **`upload` field widget** — `type:"upload"` / `widget:"upload"` action fields
25
+ now render a themed file picker (semantic tokens) that POSTs the file to the
26
+ host upload endpoint as multipart and stores the returned file url/path as the
27
+ field value. Honors `accept` and `maxSize` (tolerates kernel snake_case
28
+ `max_size`/`storage_path`). Wired into BOTH the standalone `DynamicForm`
29
+ renderer and the `ActionModalDispatcher` renderer so they stay in sync.
30
+
31
+ All purely additive — zero behavioural change for existing relations, forms, and
32
+ action modals.
33
+
34
+ ## 13.5.2
35
+
36
+ ### Patch Changes
37
+
38
+ - bc99aec: Gate per-row table actions by the row's `status` against the action's `requiresState`.
39
+
40
+ Row actions that declare a non-empty `requiresState` (camelCase or the snake_case
41
+ `requires_state` served by the backend) are now hidden in the row-action dropdown
42
+ unless the row's `status` value is one of the declared states. For example, an
43
+ "Iniciar trabajo" action with `requiresState: ['reception']` no longer appears on an
44
+ order already in `in_progress`.
45
+
46
+ Additive and null-safe: actions without `requiresState` (or an empty array) are always
47
+ shown, and rows without a `status` field surface every action, so there is no
48
+ regression for existing models.
49
+
3
50
  ## 13.5.1
4
51
 
5
52
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AA8CA,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"}
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 });
@@ -14,6 +14,19 @@ export interface DynamicColumnsHelpers {
14
14
  */
15
15
  apiBaseUrl?: string;
16
16
  }
17
+ /**
18
+ * State-machine gate for per-row actions.
19
+ *
20
+ * An action that declares a non-empty `requiresState` (camelCase) / `requires_state`
21
+ * (snake_case, as served by some backends) is only surfaced for rows whose `status`
22
+ * field value is contained in that array. This hides e.g. an "Iniciar trabajo"
23
+ * action (requiresState: ['reception']) on an order already in `in_progress`.
24
+ *
25
+ * Null-safe & non-regressive:
26
+ * - action without requiresState (or empty array) → always shown.
27
+ * - row with no `status` field → all actions shown.
28
+ */
29
+ export declare const isActionAllowedForRowState: (action: any, row: any) => boolean;
17
30
  /**
18
31
  * Builds the canonical column factory used by `<DynamicTable>` when the host
19
32
  * does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAwHD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAqXnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAOD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAmHD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAsXnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -21,6 +21,27 @@ import { DynamicIcon } from './dynamic-icon';
21
21
  import { isColumnVisibleInTable } from './column-visibility';
22
22
  const defaultGetImageUrl = (path) => path;
23
23
  const getNestedValue = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
24
+ /**
25
+ * State-machine gate for per-row actions.
26
+ *
27
+ * An action that declares a non-empty `requiresState` (camelCase) / `requires_state`
28
+ * (snake_case, as served by some backends) is only surfaced for rows whose `status`
29
+ * field value is contained in that array. This hides e.g. an "Iniciar trabajo"
30
+ * action (requiresState: ['reception']) on an order already in `in_progress`.
31
+ *
32
+ * Null-safe & non-regressive:
33
+ * - action without requiresState (or empty array) → always shown.
34
+ * - row with no `status` field → all actions shown.
35
+ */
36
+ export const isActionAllowedForRowState = (action, row) => {
37
+ const requires = action?.requiresState ?? action?.requires_state;
38
+ if (!Array.isArray(requires) || requires.length === 0)
39
+ return true;
40
+ const status = row?.status;
41
+ if (status === undefined || status === null || status === '')
42
+ return true;
43
+ return requires.map(String).includes(String(status));
44
+ };
24
45
  const lowerFirst = (value) => {
25
46
  if (!value)
26
47
  return value;
@@ -311,6 +332,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
311
332
  maxSize: 80,
312
333
  meta: {},
313
334
  cell: ({ row }) => (_jsx("div", { className: "flex items-center justify-end", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", className: "h-8 w-8 p-0", children: [_jsx("span", { className: "sr-only", children: "Abrir men\u00FA" }), _jsx(MoreHorizontal, { className: "h-4 w-4" })] }) }), _jsx(DropdownMenuContent, { align: "end", children: resolvedActions
335
+ .filter((action) => isActionAllowedForRowState(action, row.original))
314
336
  .filter((action) => {
315
337
  if (!action.condition)
316
338
  return true;
@@ -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,CAa3D"}
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
+ }
@@ -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;AAM9B,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;AAEvD,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"}
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"}
@@ -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,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;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"}
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
- return { [`f_${foreignKey}`]: `eq:${String(parentId)}` };
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"}
@@ -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
- }, [api, dataEndpoint, foreignKey, parentId, metadata, model, cacheMetadata]);
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
- const hidden = new Set([foreignKey, ...hiddenColumns]);
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
- const payload = buildCreatePayload(foreignKey, parentId, values);
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
- }, [api, dataEndpoint, editingRow, fetchAll, foreignKey, onChange, parentId]);
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
- }, [api, pivotPath, foreignKey, parentId, references, targetMeta, cacheMetadata, useResolver, legacyTargetPath]);
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
- }, [api, canCreate, canDelete, fetchPivotAndMeta, useResolver, resolved, foreignKey, onChange, parentId, pivotIndex, pivotPath, refKey, selectedIds, syncing]);
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
+ }
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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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';