@asteby/metacore-runtime-react 37.0.1 → 37.0.3
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 +12 -0
- package/dist/addon-loader.d.ts.map +1 -1
- package/dist/addon-loader.js +18 -10
- package/dist/dynamic-form-schema.d.ts +27 -0
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +79 -0
- package/dist/dynamic-table.d.ts +1 -8
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +100 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model-action-toolbar.d.ts +16 -1
- package/dist/model-action-toolbar.d.ts.map +1 -1
- package/dist/model-action-toolbar.js +30 -2
- package/dist/types.d.ts +7 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/model-action-toolbar-i18n.test.tsx +29 -6
- package/src/__tests__/visible-when.test.ts +41 -1
- package/src/addon-loader.tsx +22 -10
- package/src/dynamic-form-schema.ts +81 -0
- package/src/dynamic-table.tsx +89 -27
- package/src/index.ts +3 -0
- package/src/model-action-toolbar.tsx +42 -3
- package/src/types.ts +7 -6
package/dist/types.d.ts
CHANGED
|
@@ -309,12 +309,13 @@ export interface ColumnDefinition {
|
|
|
309
309
|
/** snake_case alias served by the kernel for `itemFields`. */
|
|
310
310
|
item_fields?: ColumnItemField[];
|
|
311
311
|
/**
|
|
312
|
-
* Conditional visibility
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
312
|
+
* Conditional visibility (kernel v3 `Column.visible_when`):
|
|
313
|
+
* - create/edit modal → `evaluateVisibleWhen` against live form values
|
|
314
|
+
* - list/board → `evaluateVisibleWhenForListScope` against known filter
|
|
315
|
+
* scope (`defaultFilters` / single-eq chips), so a locked nav scope
|
|
316
|
+
* like `party_type=customer` hides `supplier_id` without each nav
|
|
317
|
+
* item re-declaring a full column allowlist.
|
|
318
|
+
* Tolerates the camelCase alias. Absent = always visible.
|
|
318
319
|
*/
|
|
319
320
|
visible_when?: VisibleWhen;
|
|
320
321
|
/** camelCase alias for `visible_when`. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA;gCACgC;AAChC,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAA;IAC1B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,aAAa,EAAE,CAAA;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACxD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,OAAO,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC3D,CAAA;CACJ;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC1D;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACb;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,EAAE,aAAa,GAAG,cAAc,CAAA;IACpC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;IACtF,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjF,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EACE,MAAM,GACN,QAAQ,GACR,MAAM,GAGN,UAAU,GACV,WAAW,GACX,aAAa,GACb,QAAQ,GACR,QAAQ,GACR,qBAAqB,GACrB,QAAQ,GACR,SAAS,GACT,OAAO,GACP,eAAe,GACf,OAAO,GAEP,aAAa,GAEb,KAAK,GACL,MAAM,GACN,OAAO,GACP,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,eAAe,GACf,SAAS,GACT,MAAM,GAKN,UAAU,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;IAC7F,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3E;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,GAAG,MAAM,CAAA;IACrC;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;IAC/B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAA;IACxC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAOD,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAID,MAAM,MAAM,WAAW,GACjB,MAAM,GACN,UAAU,GACV,UAAU,GACV,OAAO,GACP,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,gBAAgB,GAChB,QAAQ,GACR,QAAQ,GACR,MAAM,CAAA;AAEZ;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACvB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,SAAS,EAAE,CAAA;IACrB,YAAY,CAAC,EAAE,GAAG,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,eAAe,GAAG,MAAM,CAAA;IACrC,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAA;IAClC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,kBAAkB,CAAA;IACnC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kCAAkC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,cAAc,EAAE,CAAA;IAC7B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAC/B,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;CACzC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,CAAC,CAAA;IACP,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CAChB;AAKD,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,cAAc,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;CACzC"}
|
package/package.json
CHANGED
|
@@ -56,12 +56,13 @@ describe('ModelActionToolbar — translates addon action labels at render', () =
|
|
|
56
56
|
const inst = makeI18n()
|
|
57
57
|
renderToolbar(inst)
|
|
58
58
|
|
|
59
|
-
// Before the addon bundle loads the toolbar
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
// Before the addon bundle loads the toolbar must NOT flash the full
|
|
60
|
+
// dotted key — humanizeActionLabel turns the last segment into a
|
|
61
|
+
// readable fallback until addResourceBundle fires.
|
|
62
|
+
expect(screen.getByText('Create Issue')).toBeTruthy()
|
|
62
63
|
expect(
|
|
63
|
-
screen.
|
|
64
|
-
).
|
|
64
|
+
screen.queryByText('integration_github.action.create_issue.label'),
|
|
65
|
+
).toBeNull()
|
|
65
66
|
|
|
66
67
|
// The addon locale bundle arrives asynchronously (OpsAddonLocaleLoader
|
|
67
68
|
// fetch → addResourceBundle). It is NESTED, as the host loader now merges
|
|
@@ -79,8 +80,30 @@ describe('ModelActionToolbar — translates addon action labels at render', () =
|
|
|
79
80
|
// The toolbar must re-render and show the translated label — the exact
|
|
80
81
|
// behaviour that was missing (a bare {a.label} never re-derived).
|
|
81
82
|
await waitFor(() => expect(screen.getByText('Crear issue')).toBeTruthy())
|
|
83
|
+
expect(screen.queryByText('Create Issue')).toBeNull()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('humanizes collect_payment_create without flashing the receivables key', () => {
|
|
87
|
+
const inst = makeI18n()
|
|
88
|
+
render(
|
|
89
|
+
<I18nextProvider i18n={inst}>
|
|
90
|
+
<ApiProvider client={api}>
|
|
91
|
+
<ModelActionToolbar
|
|
92
|
+
model="account_statements"
|
|
93
|
+
actions={[
|
|
94
|
+
{
|
|
95
|
+
key: 'collect_payment_create',
|
|
96
|
+
label: 'receivables.action.collect_payment_create',
|
|
97
|
+
placement: 'create',
|
|
98
|
+
} as ActionDefinition,
|
|
99
|
+
]}
|
|
100
|
+
/>
|
|
101
|
+
</ApiProvider>
|
|
102
|
+
</I18nextProvider>,
|
|
103
|
+
)
|
|
104
|
+
expect(screen.getByText('Collect Payment Create')).toBeTruthy()
|
|
82
105
|
expect(
|
|
83
|
-
screen.queryByText('
|
|
106
|
+
screen.queryByText('receivables.action.collect_payment_create'),
|
|
84
107
|
).toBeNull()
|
|
85
108
|
})
|
|
86
109
|
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
evaluateVisibleWhen,
|
|
4
|
+
evaluateVisibleWhenForListScope,
|
|
5
|
+
buildListScopeValues,
|
|
6
|
+
scopeValueFromFilterToken,
|
|
7
|
+
getVisibleWhen,
|
|
8
|
+
} from '../dynamic-form-schema'
|
|
3
9
|
import type { VisibleWhen } from '../types'
|
|
4
10
|
|
|
5
11
|
// The discount_rules use case: a `rule_scope` field decides which picker shows.
|
|
6
12
|
const equalsProduct: VisibleWhen = { field: 'rule_scope', equals: 'product' }
|
|
7
13
|
const inCategory: VisibleWhen = { field: 'rule_scope', in: ['category'] }
|
|
8
14
|
const inMulti: VisibleWhen = { field: 'scope', in: ['a', 'b'] }
|
|
15
|
+
const customerOnly: VisibleWhen = { field: 'party_type', equals: 'customer' }
|
|
16
|
+
const supplierOnly: VisibleWhen = { field: 'party_type', equals: 'supplier' }
|
|
9
17
|
|
|
10
18
|
describe('evaluateVisibleWhen', () => {
|
|
11
19
|
it('no predicate → always visible', () => {
|
|
@@ -61,3 +69,35 @@ describe('getVisibleWhen', () => {
|
|
|
61
69
|
expect(getVisibleWhen({ visible_when: { equals: 'x' } as unknown as VisibleWhen })).toBeUndefined()
|
|
62
70
|
})
|
|
63
71
|
})
|
|
72
|
+
|
|
73
|
+
describe('scopeValueFromFilterToken / buildListScopeValues', () => {
|
|
74
|
+
it('strips eq: and leaves bare values', () => {
|
|
75
|
+
expect(scopeValueFromFilterToken('eq:customer')).toBe('customer')
|
|
76
|
+
expect(scopeValueFromFilterToken('customer')).toBe('customer')
|
|
77
|
+
expect(scopeValueFromFilterToken('IN:a,b')).toBe('IN:a,b')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('merges defaultFilters over dynamic and skips multi-value chips', () => {
|
|
81
|
+
expect(
|
|
82
|
+
buildListScopeValues(
|
|
83
|
+
{ party_type: 'eq:customer', branch_id: 'eq:b1' },
|
|
84
|
+
{ party_type: ['supplier'], status: ['open', 'draft'], warehouse: ['w1'] },
|
|
85
|
+
),
|
|
86
|
+
).toEqual({ party_type: 'customer', branch_id: 'b1', warehouse: 'w1' })
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('evaluateVisibleWhenForListScope', () => {
|
|
91
|
+
it('keeps columns when the governing field is unknown (mixed list)', () => {
|
|
92
|
+
expect(evaluateVisibleWhenForListScope(customerOnly, {})).toBe(true)
|
|
93
|
+
expect(evaluateVisibleWhenForListScope(supplierOnly, {})).toBe(true)
|
|
94
|
+
expect(evaluateVisibleWhenForListScope(customerOnly, { other: 'x' })).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('hides the opposite party column when nav locks party_type', () => {
|
|
98
|
+
expect(evaluateVisibleWhenForListScope(customerOnly, { party_type: 'customer' })).toBe(true)
|
|
99
|
+
expect(evaluateVisibleWhenForListScope(supplierOnly, { party_type: 'customer' })).toBe(false)
|
|
100
|
+
expect(evaluateVisibleWhenForListScope(customerOnly, { party_type: 'supplier' })).toBe(false)
|
|
101
|
+
expect(evaluateVisibleWhenForListScope(supplierOnly, { party_type: 'supplier' })).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
})
|
package/src/addon-loader.tsx
CHANGED
|
@@ -93,18 +93,30 @@ function isRuntimeNotReady(e: unknown): boolean {
|
|
|
93
93
|
return msg.includes('#RUNTIME-009') || msg.includes('call createInstance')
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
/** Transient network / gateway failures while the host API is restarting. */
|
|
97
|
+
function isTransientRemoteLoadError(e: unknown): boolean {
|
|
98
|
+
if (isRuntimeNotReady(e)) return true
|
|
99
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
100
|
+
return (
|
|
101
|
+
/failed to fetch|networkerror|load failed|502|503|504|ERR_CONNECTION_REFUSED|ECONNREFUSED|Outdated Optimize Dep/i.test(
|
|
102
|
+
msg,
|
|
103
|
+
) ||
|
|
104
|
+
// MF wraps fetch failures variously
|
|
105
|
+
/ScriptExternalLoadError|Loading script failed|remoteEntry/i.test(msg)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Retry host-boot races (RUNTIME-009) and brief API blips (502 while backend
|
|
110
|
+
// SyncAll is still running). Permanent errors (404, missing register) fail fast.
|
|
111
|
+
async function withRemoteLoadRetry<T>(op: () => T | Promise<T>): Promise<T> {
|
|
112
|
+
const maxAttempts = 8
|
|
102
113
|
for (let attempt = 1; ; attempt++) {
|
|
103
114
|
try {
|
|
104
115
|
return await op()
|
|
105
116
|
} catch (e) {
|
|
106
|
-
if (!
|
|
107
|
-
|
|
117
|
+
if (!isTransientRemoteLoadError(e) || attempt >= maxAttempts) throw e
|
|
118
|
+
// 80, 160, 320… capped — ~3s worst case for API restart
|
|
119
|
+
await sleep(Math.min(80 * 2 ** (attempt - 1), 800))
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
}
|
|
@@ -118,12 +130,12 @@ async function loadAddon(
|
|
|
118
130
|
// fetches the new remoteEntry. `force: true` wipes that remote's module
|
|
119
131
|
// cache — without it, loadRemote would keep serving the previous bundle.
|
|
120
132
|
if (shouldReregisterRemote(scope, url)) {
|
|
121
|
-
await
|
|
133
|
+
await withRemoteLoadRetry(() =>
|
|
122
134
|
registerRemotes([{ name: scope, entry: url, type: 'module' }], { force: true }),
|
|
123
135
|
)
|
|
124
136
|
markRemoteRegistered(scope, url)
|
|
125
137
|
}
|
|
126
|
-
return
|
|
138
|
+
return withRemoteLoadRetry(() =>
|
|
127
139
|
loadRemote<AddonRegisterModule>(remoteId(scope, module)),
|
|
128
140
|
)
|
|
129
141
|
}
|
|
@@ -382,6 +382,87 @@ export function evaluateVisibleWhen(
|
|
|
382
382
|
return true
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Strip a DynamicTable / URL filter token down to the comparable scalar the
|
|
387
|
+
* kernel `visible_when.equals` / `.in` predicates expect.
|
|
388
|
+
*
|
|
389
|
+
* `eq:customer` → `customer`, bare `customer` stays, `in:a,b` keeps the raw
|
|
390
|
+
* multi-value (list-scope only gates on known single-eq scopes today).
|
|
391
|
+
*/
|
|
392
|
+
export function scopeValueFromFilterToken(raw: unknown): string {
|
|
393
|
+
if (raw == null) return ''
|
|
394
|
+
const s = String(raw)
|
|
395
|
+
const i = s.indexOf(':')
|
|
396
|
+
if (i <= 0) return s
|
|
397
|
+
const op = s.slice(0, i).toLowerCase()
|
|
398
|
+
const rest = s.slice(i + 1)
|
|
399
|
+
if (
|
|
400
|
+
op === 'eq' ||
|
|
401
|
+
op === 'neq' ||
|
|
402
|
+
op === 'gt' ||
|
|
403
|
+
op === 'gte' ||
|
|
404
|
+
op === 'lt' ||
|
|
405
|
+
op === 'lte' ||
|
|
406
|
+
op === 'like' ||
|
|
407
|
+
op === 'ilike' ||
|
|
408
|
+
op === 'contains'
|
|
409
|
+
) {
|
|
410
|
+
return rest
|
|
411
|
+
}
|
|
412
|
+
return s
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Flat "known field → value" map for list/board surfaces. Built from locked
|
|
417
|
+
* `defaultFilters` (nav / branch scope) plus active `dynamicFilters`. Only
|
|
418
|
+
* fields present here are considered known — see
|
|
419
|
+
* `evaluateVisibleWhenForListScope`.
|
|
420
|
+
*/
|
|
421
|
+
export function buildListScopeValues(
|
|
422
|
+
defaultFilters?: Record<string, unknown> | null,
|
|
423
|
+
dynamicFilters?: Record<string, string[] | undefined> | null,
|
|
424
|
+
): Record<string, string> {
|
|
425
|
+
const out: Record<string, string> = {}
|
|
426
|
+
if (defaultFilters) {
|
|
427
|
+
for (const [key, value] of Object.entries(defaultFilters)) {
|
|
428
|
+
const v = scopeValueFromFilterToken(value)
|
|
429
|
+
if (v !== '') out[key] = v
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (dynamicFilters) {
|
|
433
|
+
for (const [key, values] of Object.entries(dynamicFilters)) {
|
|
434
|
+
if (defaultFilters && key in defaultFilters) continue
|
|
435
|
+
if (!values || values.length !== 1) continue
|
|
436
|
+
const v = scopeValueFromFilterToken(values[0])
|
|
437
|
+
if (v !== '') out[key] = v
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return out
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* List/board variant of `evaluateVisibleWhen`.
|
|
445
|
+
*
|
|
446
|
+
* Forms always have a live sibling value (or ''). Lists often do not — a
|
|
447
|
+
* mixed AccountStatement table has no single `party_type`. Hiding every
|
|
448
|
+
* `visible_when` column when the governing field is unknown would wipe both
|
|
449
|
+
* Cliente and Proveedor on the unscoped view. Rule: if the governing field is
|
|
450
|
+
* not in `scope` (or is empty), keep the column; once the scope pins it
|
|
451
|
+
* (sidebar locked_scope / defaultFilters / a single-eq chip), apply the same
|
|
452
|
+
* predicate as the form.
|
|
453
|
+
*/
|
|
454
|
+
export function evaluateVisibleWhenForListScope(
|
|
455
|
+
cond: VisibleWhen | null | undefined,
|
|
456
|
+
scope: Record<string, any> | null | undefined,
|
|
457
|
+
): boolean {
|
|
458
|
+
if (!cond || typeof cond.field !== 'string' || cond.field.trim() === '') return true
|
|
459
|
+
const key = cond.field.trim()
|
|
460
|
+
if (!scope || !(key in scope)) return true
|
|
461
|
+
const raw = scope[key]
|
|
462
|
+
if (raw == null || String(raw) === '') return true
|
|
463
|
+
return evaluateVisibleWhen(cond, scope)
|
|
464
|
+
}
|
|
465
|
+
|
|
385
466
|
/**
|
|
386
467
|
* Reads a field's enriched options-resolution config, tolerating the camelCase
|
|
387
468
|
* `optionsConfig` (authored SDK shape) and the snake_case `options_config` the
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -64,20 +64,25 @@ import { toast } from 'sonner'
|
|
|
64
64
|
import { Progress } from './dialogs/_primitives'
|
|
65
65
|
import { useMetadataCache } from './metadata-cache'
|
|
66
66
|
import { useApi, useCurrentBranch } from './api-context'
|
|
67
|
-
import { useRealtimeDefault, useRealtimeTick } from './realtime-context'
|
|
68
67
|
import type { ColumnFilterConfig, GetDynamicColumns } from './dynamic-columns-shim'
|
|
69
68
|
import { defaultGetDynamicColumns, DATE_CELL_TYPES, aggregateOf, formatAggregateTotal } from './dynamic-columns'
|
|
70
69
|
import { useFacetLoaders, isLongTextColumn } from './use-facet-loaders'
|
|
71
70
|
import { translateOptionLabels } from './filter-chips'
|
|
72
71
|
import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll'
|
|
73
72
|
import { OptionsContext } from './options-context'
|
|
74
|
-
import type { TableMetadata, ApiResponse } from './types'
|
|
73
|
+
import type { TableMetadata, ApiResponse, ColumnDefinition } from './types'
|
|
75
74
|
import { getSearchableColumnKeys } from './column-visibility'
|
|
76
75
|
import { useDebouncedValue } from './use-debounced-value'
|
|
77
76
|
import { useCan, usePermissionsActive, gateTableMetadata } from './permissions-context'
|
|
78
77
|
import { useDynamicRowActions } from './dynamic-row-actions'
|
|
79
78
|
import { ExportDialog } from './dialogs/export'
|
|
80
79
|
import { ImportDialog } from './dialogs/import'
|
|
80
|
+
import {
|
|
81
|
+
buildListScopeValues,
|
|
82
|
+
evaluateVisibleWhenForListScope,
|
|
83
|
+
getVisibleWhen,
|
|
84
|
+
scopeValueFromFilterToken,
|
|
85
|
+
} from './dynamic-form-schema'
|
|
81
86
|
|
|
82
87
|
// ---------------------------------------------------------------------------
|
|
83
88
|
// Row-data cache (perceived performance).
|
|
@@ -197,13 +202,6 @@ export interface DynamicTableProps {
|
|
|
197
202
|
*/
|
|
198
203
|
onRowClick?: (row: any) => void
|
|
199
204
|
refreshTrigger?: any
|
|
200
|
-
/**
|
|
201
|
-
* Refetch when the host's realtime client reports a data event for this
|
|
202
|
-
* model (created/updated/deleted by anyone in the org, or a `resync`).
|
|
203
|
-
* Off by default; `<RealtimeProvider defaultRealtime>` turns it on for
|
|
204
|
-
* every table, and an explicit prop always wins. No-op without a client.
|
|
205
|
-
*/
|
|
206
|
-
realtime?: boolean
|
|
207
205
|
defaultFilters?: Record<string, any>
|
|
208
206
|
extraColumns?: ColumnDef<any>[]
|
|
209
207
|
/**
|
|
@@ -261,7 +259,6 @@ export function DynamicTable({
|
|
|
261
259
|
onAction,
|
|
262
260
|
onRowClick,
|
|
263
261
|
refreshTrigger,
|
|
264
|
-
realtime: realtimeProp,
|
|
265
262
|
defaultFilters,
|
|
266
263
|
extraColumns = [],
|
|
267
264
|
getDynamicColumns = defaultGetDynamicColumns,
|
|
@@ -276,13 +273,6 @@ export function DynamicTable({
|
|
|
276
273
|
const { t, i18n } = useTranslation()
|
|
277
274
|
const api = useApi()
|
|
278
275
|
const currentBranch = useCurrentBranch()
|
|
279
|
-
// Realtime refetch (opt-in): a debounced counter that bumps on every
|
|
280
|
-
// DATA_EVENT for this model and rides the same deps as `refreshTrigger`.
|
|
281
|
-
const realtimeDefault = useRealtimeDefault()
|
|
282
|
-
const realtimeTick = useRealtimeTick({
|
|
283
|
-
models: [model],
|
|
284
|
-
enabled: realtimeProp ?? realtimeDefault,
|
|
285
|
-
})
|
|
286
276
|
|
|
287
277
|
const prevBranchId = useRef(currentBranch?.id)
|
|
288
278
|
|
|
@@ -512,6 +502,22 @@ export function DynamicTable({
|
|
|
512
502
|
if (values.length === 1) params.set(`f_${key}`, internalValueToUrl(values[0]))
|
|
513
503
|
else params.set(`f_${key}`, `in:${values.join(',')}`)
|
|
514
504
|
})
|
|
505
|
+
// Re-pin locked scope into the URL so sidebar matching + deep-links keep
|
|
506
|
+
// working after "Limpiar filtros" (those keys are not in dynamicFilters).
|
|
507
|
+
if (defaultFilters) {
|
|
508
|
+
Object.entries(defaultFilters).forEach(([key, value]) => {
|
|
509
|
+
params.set(`f_${key}`, internalValueToUrl(String(value ?? '')))
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
// Preserve f_* already in the location that we did not rebuild above.
|
|
513
|
+
// Sidebar deep-links (CxC→CxP) push f_party_type=eq:supplier before React
|
|
514
|
+
// re-renders with matching defaultFilters; without this carry-through the
|
|
515
|
+
// write effect races and strips the filter down to bare ?view=list.
|
|
516
|
+
current.forEach((value, key) => {
|
|
517
|
+
if (!key.startsWith('f_')) return
|
|
518
|
+
if (params.has(key)) return
|
|
519
|
+
params.set(key, value)
|
|
520
|
+
})
|
|
515
521
|
const search = params.toString()
|
|
516
522
|
// If what we'd write is semantically identical to what's already in the
|
|
517
523
|
// bar (only key order / colon-encoding differ from the router's form),
|
|
@@ -695,6 +701,52 @@ export function DynamicTable({
|
|
|
695
701
|
!hideImport && (viewMetadata?.canImport ?? Boolean(viewMetadata?.import?.columns?.length))
|
|
696
702
|
const exportEnabled = !hideExport && Boolean(viewMetadata?.canExport)
|
|
697
703
|
|
|
704
|
+
const listScopeValues = useMemo(() => {
|
|
705
|
+
const base = buildListScopeValues(defaultFilters, dynamicFilters)
|
|
706
|
+
// Deep-links pin f_* in the URL before defaultFilters / nav matching
|
|
707
|
+
// catch up; adopt single-eq scope values so visible_when columns hide
|
|
708
|
+
// on first paint (CxC hides Proveedor without waiting for navFilter).
|
|
709
|
+
if (!enableUrlSync || typeof window === 'undefined') return base
|
|
710
|
+
const params = new URLSearchParams(window.location.search)
|
|
711
|
+
const fromUrl: Record<string, string> = {}
|
|
712
|
+
params.forEach((raw, key) => {
|
|
713
|
+
if (!key.startsWith('f_')) return
|
|
714
|
+
const col = key.slice(2)
|
|
715
|
+
if (!col || col in base) return
|
|
716
|
+
const v = scopeValueFromFilterToken(raw)
|
|
717
|
+
if (!v) return
|
|
718
|
+
// Multi-value / range tokens are not a single list-scope pin.
|
|
719
|
+
if (v.includes(',') || /^(in|not_in|range|gte|lte):/i.test(String(raw))) return
|
|
720
|
+
fromUrl[col] = v
|
|
721
|
+
})
|
|
722
|
+
return Object.keys(fromUrl).length === 0 ? base : { ...fromUrl, ...base }
|
|
723
|
+
}, [defaultFilters, dynamicFilters, enableUrlSync, urlSynced])
|
|
724
|
+
|
|
725
|
+
// Columns whose kernel `visible_when` fails against the known list scope
|
|
726
|
+
// (locked nav defaultFilters, single-eq chips). Host `hiddenColumns` still
|
|
727
|
+
// wins; this is the scalable path so CxC hides Proveedor without each nav
|
|
728
|
+
// item re-listing every allowed column.
|
|
729
|
+
const scopeHiddenColumns = useMemo(() => {
|
|
730
|
+
const cols = (viewMetadata?.columns ?? metadata?.columns ?? []) as ColumnDefinition[]
|
|
731
|
+
if (cols.length === 0 || Object.keys(listScopeValues).length === 0) return [] as string[]
|
|
732
|
+
const hidden: string[] = []
|
|
733
|
+
for (const col of cols) {
|
|
734
|
+
const key = col.key
|
|
735
|
+
if (!key) continue
|
|
736
|
+
if (!evaluateVisibleWhenForListScope(getVisibleWhen(col), listScopeValues)) {
|
|
737
|
+
hidden.push(key)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return hidden
|
|
741
|
+
}, [metadata, viewMetadata, listScopeValues])
|
|
742
|
+
|
|
743
|
+
const effectiveHiddenColumns = useMemo(() => {
|
|
744
|
+
if (scopeHiddenColumns.length === 0) return hiddenColumns
|
|
745
|
+
const set = new Set(hiddenColumns)
|
|
746
|
+
for (const k of scopeHiddenColumns) set.add(k)
|
|
747
|
+
return Array.from(set)
|
|
748
|
+
}, [hiddenColumns, scopeHiddenColumns])
|
|
749
|
+
|
|
698
750
|
const buildFilterParams = useCallback(() => {
|
|
699
751
|
const params: Record<string, any> = {}
|
|
700
752
|
if (sorting.length > 0) {
|
|
@@ -710,9 +762,12 @@ export function DynamicTable({
|
|
|
710
762
|
}
|
|
711
763
|
// searchableKeys === [] → drop the search request entirely
|
|
712
764
|
}
|
|
713
|
-
columnFilters.forEach((filter: { id: string; value: unknown }) => {
|
|
714
|
-
|
|
765
|
+
columnFilters.forEach((filter: { id: string; value: unknown }) => {
|
|
766
|
+
if (defaultFilters && filter.id in defaultFilters) return
|
|
767
|
+
params[`f_${filter.id}`] = filter.value
|
|
768
|
+
})
|
|
715
769
|
Object.entries(dynamicFilters).forEach(([key, values]) => {
|
|
770
|
+
if (defaultFilters && key in defaultFilters) return
|
|
716
771
|
if (values.length === 0) return
|
|
717
772
|
const gteVal = values.find(v => v.startsWith('GTE:'))
|
|
718
773
|
const lteVal = values.find(v => v.startsWith('LTE:'))
|
|
@@ -725,6 +780,8 @@ export function DynamicTable({
|
|
|
725
780
|
if (values.length === 1) params[`f_${key}`] = values[0]
|
|
726
781
|
else params[`f_${key}`] = `IN:${values.join(',')}`
|
|
727
782
|
})
|
|
783
|
+
// Locked scope last so it always wins over stale dynamic/column filters.
|
|
784
|
+
if (defaultFilters) Object.entries(defaultFilters).forEach(([key, value]) => { params[`f_${key}`] = value })
|
|
728
785
|
if (dateRange?.from) {
|
|
729
786
|
const startDate = format(dateRange.from, 'yyyy-MM-dd')
|
|
730
787
|
const endDate = dateRange.to ? format(dateRange.to, 'yyyy-MM-dd') : startDate
|
|
@@ -770,8 +827,7 @@ export function DynamicTable({
|
|
|
770
827
|
} finally {
|
|
771
828
|
setLoadingData(false)
|
|
772
829
|
}
|
|
773
|
-
|
|
774
|
-
}, [model, metadata, pagination, buildFilterParams, refreshTrigger, realtimeTick, endpoint, currentBranch?.id, api, enableUrlSync])
|
|
830
|
+
}, [model, metadata, pagination, buildFilterParams, refreshTrigger, endpoint, currentBranch?.id, api, enableUrlSync])
|
|
775
831
|
|
|
776
832
|
// Columns whose metadata opts into a footer total (display_config.aggregate
|
|
777
833
|
// → styleConfig.aggregate). When empty, no footer row is rendered and no
|
|
@@ -940,9 +996,8 @@ export function DynamicTable({
|
|
|
940
996
|
// matching the classic path (fetchData carries refreshTrigger in its
|
|
941
997
|
// deps). Without it the comment above lied: infinite lists silently
|
|
942
998
|
// failed to reload after a create ("a veces no recarga la tabla").
|
|
943
|
-
// realtimeTick plays the same role for data events (see the `realtime` prop).
|
|
944
999
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
945
|
-
}, [infiniteScroll, metadata, filterSignature, refreshTrigger
|
|
1000
|
+
}, [infiniteScroll, metadata, filterSignature, refreshTrigger])
|
|
946
1001
|
|
|
947
1002
|
const handleRefresh = useCallback(() => {
|
|
948
1003
|
// Infinite mode owns its own list: refresh reloads page 1 and drops the
|
|
@@ -997,9 +1052,11 @@ export function DynamicTable({
|
|
|
997
1052
|
}
|
|
998
1053
|
|
|
999
1054
|
const handleDynamicFilterChange = useCallback((filterKey: string, values: string[]) => {
|
|
1055
|
+
// Locked scope (nav / branch defaultFilters) cannot be changed from the UI.
|
|
1056
|
+
if (defaultFilters && filterKey in defaultFilters) return
|
|
1000
1057
|
setDynamicFilters((prev: Record<string, string[]>) => ({ ...prev, [filterKey]: values }))
|
|
1001
1058
|
setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 }))
|
|
1002
|
-
}, [])
|
|
1059
|
+
}, [defaultFilters])
|
|
1003
1060
|
|
|
1004
1061
|
// Same facet loader machinery the board uses, so a text column filters
|
|
1005
1062
|
// identically in the table header and the kanban Sheet (the host's
|
|
@@ -1026,6 +1083,9 @@ export function DynamicTable({
|
|
|
1026
1083
|
// `filterable: true` — keeps the kernel API minimal (one flag on the
|
|
1027
1084
|
// column) while still rendering the FilterableColumnHeader.
|
|
1028
1085
|
for (const f of metadata.filters ?? []) {
|
|
1086
|
+
const filterCol = f.column || f.key
|
|
1087
|
+
if (defaultFilters && filterCol in defaultFilters) continue
|
|
1088
|
+
if (effectiveHiddenColumns.includes(filterCol)) continue
|
|
1029
1089
|
let fType = f.type as ColumnFilterConfig['filterType']
|
|
1030
1090
|
let options: { label: string; value: string; icon?: string; color?: string }[] = []
|
|
1031
1091
|
if (f.options && f.options.length > 0) {
|
|
@@ -1069,6 +1129,8 @@ export function DynamicTable({
|
|
|
1069
1129
|
}
|
|
1070
1130
|
for (const c of metadata.columns ?? []) {
|
|
1071
1131
|
if (!c.filterable || map.has(c.key)) continue
|
|
1132
|
+
if (defaultFilters && c.key in defaultFilters) continue
|
|
1133
|
+
if (effectiveHiddenColumns.includes(c.key)) continue
|
|
1072
1134
|
const hasStaticOptions = (c.options?.length ?? 0) > 0
|
|
1073
1135
|
const hasEndpoint = !!c.searchEndpoint
|
|
1074
1136
|
const isRelation = !!c.ref || c.filterType === 'dynamic_select'
|
|
@@ -1133,7 +1195,7 @@ export function DynamicTable({
|
|
|
1133
1195
|
})
|
|
1134
1196
|
}
|
|
1135
1197
|
return map
|
|
1136
|
-
}, [metadata, filterOptionsMap, dynamicFilters, handleDynamicFilterChange, facetsBase, getFacetLoader, facetOptions, t])
|
|
1198
|
+
}, [metadata, filterOptionsMap, dynamicFilters, handleDynamicFilterChange, facetsBase, getFacetLoader, facetOptions, t, defaultFilters, effectiveHiddenColumns])
|
|
1137
1199
|
|
|
1138
1200
|
// Prewarm every facet field once the configs settle, so a text column's
|
|
1139
1201
|
// header filter opens instantly with values + counts (same as the kanban).
|
|
@@ -1170,11 +1232,11 @@ export function DynamicTable({
|
|
|
1170
1232
|
return actions === viewMetadata.actions ? viewMetadata : { ...viewMetadata, actions }
|
|
1171
1233
|
})()
|
|
1172
1234
|
const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs, timeZone, currency)
|
|
1173
|
-
const filteredBase = baseColumns.filter((col: ColumnDef<any>) => !
|
|
1235
|
+
const filteredBase = baseColumns.filter((col: ColumnDef<any>) => !effectiveHiddenColumns.includes(col.id as string))
|
|
1174
1236
|
const actionsCol = filteredBase.find((c: ColumnDef<any>) => c.id === 'actions')
|
|
1175
1237
|
const otherCols = filteredBase.filter((c: ColumnDef<any>) => c.id !== 'actions')
|
|
1176
1238
|
return [...otherCols, ...extraColumns, ...(actionsCol ? [actionsCol] : [])]
|
|
1177
|
-
}, [viewMetadata, handleInternalAction,
|
|
1239
|
+
}, [viewMetadata, handleInternalAction, effectiveHiddenColumns, allowedActionKeys, extraColumns, t, i18n.language, columnFilterConfigs, getDynamicColumns, timeZone, currency])
|
|
1178
1240
|
|
|
1179
1241
|
const filters = useMemo(() => [], [])
|
|
1180
1242
|
|
package/src/index.ts
CHANGED
|
@@ -46,10 +46,42 @@ export interface ModelActionToolbarProps {
|
|
|
46
46
|
onChange?: () => void
|
|
47
47
|
/** Extra classes on the button row container. */
|
|
48
48
|
className?: string
|
|
49
|
+
/**
|
|
50
|
+
* Fired on hover/focus of an action button so the host can prefetch a
|
|
51
|
+
* federated remote (e.g. collections) before the click — Module Federation
|
|
52
|
+
* lazy load without blocking first paint of the list page.
|
|
53
|
+
*/
|
|
54
|
+
onActionIntent?: (action: ActionDefinition) => void
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
const DEFAULT_PLACEMENTS: ActionPlacement[] = ['table', 'create']
|
|
52
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Last-resort label when `t(key)` has no translation yet: never flash the full
|
|
61
|
+
* dotted key in a primary CTA. Prefers the action/field segment over a trailing
|
|
62
|
+
* `.label` / `.confirm` leaf.
|
|
63
|
+
* "receivables.action.collect_payment_create" → "Collect payment create"
|
|
64
|
+
* "integration_github.action.create_issue.label" → "Create issue"
|
|
65
|
+
* Already-human labels pass through unchanged.
|
|
66
|
+
*/
|
|
67
|
+
export function humanizeActionLabel(label: string): string {
|
|
68
|
+
if (!label) return label
|
|
69
|
+
if (!label.includes('.') || /\s/.test(label)) return label
|
|
70
|
+
const parts = label.split('.').filter(Boolean)
|
|
71
|
+
let leaf = parts[parts.length - 1] || label
|
|
72
|
+
if (
|
|
73
|
+
(leaf === 'label' || leaf === 'confirm' || leaf === 'title') &&
|
|
74
|
+
parts.length >= 2
|
|
75
|
+
) {
|
|
76
|
+
leaf = parts[parts.length - 2]
|
|
77
|
+
}
|
|
78
|
+
return leaf
|
|
79
|
+
.split('_')
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
82
|
+
.join(' ')
|
|
83
|
+
}
|
|
84
|
+
|
|
53
85
|
function toActionMetadata(a: ActionDefinition): ActionMetadata {
|
|
54
86
|
return {
|
|
55
87
|
key: a.key,
|
|
@@ -109,6 +141,7 @@ export function ModelActionToolbar({
|
|
|
109
141
|
placements = DEFAULT_PLACEMENTS,
|
|
110
142
|
onChange,
|
|
111
143
|
className,
|
|
144
|
+
onActionIntent,
|
|
112
145
|
}: ModelActionToolbarProps) {
|
|
113
146
|
const { t } = useTranslation()
|
|
114
147
|
const all = useModelActions(model, placements, actions)
|
|
@@ -133,7 +166,12 @@ export function ModelActionToolbar({
|
|
|
133
166
|
<Button
|
|
134
167
|
key={a.key}
|
|
135
168
|
variant={isCreate ? 'default' : 'outline'}
|
|
136
|
-
onClick={() =>
|
|
169
|
+
onClick={() => {
|
|
170
|
+
onActionIntent?.(a)
|
|
171
|
+
setActive(toActionMetadata(a))
|
|
172
|
+
}}
|
|
173
|
+
onMouseEnter={() => onActionIntent?.(a)}
|
|
174
|
+
onFocus={() => onActionIntent?.(a)}
|
|
137
175
|
style={a.color && !isCreate ? { borderColor: a.color, color: a.color } : undefined}
|
|
138
176
|
>
|
|
139
177
|
<DynamicIcon name={a.icon || (isCreate ? 'Plus' : 'Zap')} className="mr-2 h-4 w-4" />
|
|
@@ -142,8 +180,9 @@ export function ModelActionToolbar({
|
|
|
142
180
|
locale bundle loads asynchronously, so translate at render — a
|
|
143
181
|
bare `{a.label}` prints the raw key until (and after) the bundle
|
|
144
182
|
lands because nothing re-derives it. defaultValue keeps an
|
|
145
|
-
already-localized label untouched
|
|
146
|
-
|
|
183
|
+
already-localized label untouched; humanizeLastSegment avoids
|
|
184
|
+
flashing the full dotted key when the bundle is still in flight. */}
|
|
185
|
+
{t(a.label, { defaultValue: humanizeActionLabel(a.label) })}
|
|
147
186
|
</Button>
|
|
148
187
|
)
|
|
149
188
|
})}
|