@asteby/metacore-runtime-react 7.0.0 → 7.1.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 7.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0cd085c: Zero-config CRUD UX from a single column flag.
8
+
9
+ Three changes that move polish from each app's metadata into the SDK default behaviour, so a model only needs `enableCRUDActions: true` plus `filterable: true` on the columns it wants searchable to get the same UX link / ops / hub render today:
10
+ 1. **Auto-derive filter chip type from column type.** A column flagged `filterable: true` without options or a `searchEndpoint` no longer falls back to "no filter" — it picks the FilterableColumnHeader variant that matches the column type: `text` for text/email/phone/tags, `number_range` for numeric columns, `boolean` for booleans, `select` when options/endpoint are present.
11
+ 2. **Auto-render the row Actions column when `enableCRUDActions` is on.** If the host metadata already declares its own `actions[]`, those win. When it doesn't, the SDK falls back to the canonical View / Edit / Delete trio wired to DynamicTable's existing `view` / `edit` / `delete` handlers — no host-side glue.
12
+ 3. **`<DynamicCRUDPage>` defaults `hideRefresh` to `true`.** The page-level Refresh button duplicated the one DynamicTable's internal toolbar already ships next to "View"; the page chrome now defers to it. Apps that want both back can pass `hideRefresh={false}`.
13
+
3
14
  ## 7.0.0
4
15
 
5
16
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAqCA,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,CA8UnB;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":"AAqCA,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,CAiXnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -266,14 +266,47 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
266
266
  enableHiding: true,
267
267
  });
268
268
  });
269
- if (metadata.hasActions && metadata.actions.length > 0) {
269
+ // Resolve which actions to surface in the row dropdown:
270
+ // 1. If the host metadata declares its own actions, use them as-is.
271
+ // 2. Otherwise, when enableCRUDActions is true, fall back to the
272
+ // canonical View / Edit / Delete trio so any model with CRUD on
273
+ // gets the same dropdown without the host having to declare it.
274
+ // The DynamicTable wires `view`/`edit`/`delete` to its own dialogs
275
+ // through onAction, so labels/icons are the only thing this needs to
276
+ // ship.
277
+ const explicitActions = metadata.actions ?? [];
278
+ const hasExplicitActions = (metadata.hasActions ?? explicitActions.length > 0) && explicitActions.length > 0;
279
+ const defaultCRUDActions = metadata.enableCRUDActions
280
+ ? [
281
+ {
282
+ key: 'view',
283
+ name: 'view',
284
+ label: t ? t('datatable.view_record') : 'Ver',
285
+ icon: 'Eye',
286
+ },
287
+ {
288
+ key: 'edit',
289
+ name: 'edit',
290
+ label: t ? t('datatable.edit') : 'Editar',
291
+ icon: 'Pencil',
292
+ },
293
+ {
294
+ key: 'delete',
295
+ name: 'delete',
296
+ label: t ? t('datatable.delete') : 'Eliminar',
297
+ icon: 'Trash2',
298
+ },
299
+ ]
300
+ : [];
301
+ const resolvedActions = hasExplicitActions ? explicitActions : defaultCRUDActions;
302
+ if (resolvedActions.length > 0) {
270
303
  columns.push({
271
304
  id: 'actions',
272
305
  header: () => _jsx("div", { className: "text-right", children: t ? t('common.actions') : 'Acciones' }),
273
306
  size: 80,
274
307
  maxSize: 80,
275
308
  meta: {},
276
- 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: metadata.actions
309
+ 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
277
310
  .filter((action) => {
278
311
  if (!action.condition)
279
312
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAWd,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAkL1D"}
1
+ {"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAWd,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAsL1D"}
@@ -87,7 +87,11 @@ export function DynamicCRUDPage(props) {
87
87
  const effectiveHideCreate = hideCreate || ext?.hideCreate;
88
88
  const effectiveHideExport = hideExport || ext?.hideExport;
89
89
  const effectiveHideImport = hideImport || ext?.hideImport;
90
- const effectiveHideRefresh = hideRefresh || ext?.hideRefresh;
90
+ // Refresh defaults to hidden in the page header — <DynamicTable> ships
91
+ // its own refresh icon next to the View / column-visibility toolbar, so
92
+ // showing one in the page chrome is just visual duplication. Apps that
93
+ // want it back can pass `hideRefresh={false}`.
94
+ const effectiveHideRefresh = hideRefresh ?? ext?.hideRefresh ?? true;
91
95
  const showCreate = enableCRUD && !effectiveHideCreate;
92
96
  const showImport = enableCRUD && !effectiveHideImport;
93
97
  const showExport = !effectiveHideExport;
@@ -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;AASnF,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,2CA4qBnB"}
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;AASnF,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,2CAsrBnB"}
@@ -469,8 +469,20 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
469
469
  continue;
470
470
  const hasStaticOptions = (c.options?.length ?? 0) > 0;
471
471
  const hasEndpoint = !!c.searchEndpoint;
472
- if (!hasStaticOptions && !hasEndpoint && c.type !== 'boolean')
473
- continue;
472
+ // Pick the filter UI from column type:
473
+ // - explicit options or searchEndpoint → multi-select dropdown
474
+ // - boolean → boolean toggle (renders as select under the hood)
475
+ // - number / number_range / numeric → number range
476
+ // - everything else (text, email, phone, tags…) → text contains
477
+ let filterType = 'select';
478
+ if (hasStaticOptions || hasEndpoint)
479
+ filterType = 'select';
480
+ else if (c.type === 'boolean')
481
+ filterType = 'boolean';
482
+ else if (c.type === 'number')
483
+ filterType = 'number_range';
484
+ else
485
+ filterType = 'text';
474
486
  const options = hasStaticOptions
475
487
  ? c.options.map(o => ({
476
488
  label: o.label,
@@ -482,7 +494,7 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
482
494
  ? filterOptionsMap.get(c.searchEndpoint) || []
483
495
  : [];
484
496
  map.set(c.key, {
485
- filterType: 'select',
497
+ filterType,
486
498
  filterKey: c.key,
487
499
  options,
488
500
  selectedValues: dynamicFilters[c.key] || [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -461,7 +461,42 @@ export function makeDefaultGetDynamicColumns(
461
461
  })
462
462
  })
463
463
 
464
- if (metadata.hasActions && metadata.actions.length > 0) {
464
+ // Resolve which actions to surface in the row dropdown:
465
+ // 1. If the host metadata declares its own actions, use them as-is.
466
+ // 2. Otherwise, when enableCRUDActions is true, fall back to the
467
+ // canonical View / Edit / Delete trio so any model with CRUD on
468
+ // gets the same dropdown without the host having to declare it.
469
+ // The DynamicTable wires `view`/`edit`/`delete` to its own dialogs
470
+ // through onAction, so labels/icons are the only thing this needs to
471
+ // ship.
472
+ const explicitActions = metadata.actions ?? []
473
+ const hasExplicitActions = (metadata.hasActions ?? explicitActions.length > 0) && explicitActions.length > 0
474
+ const defaultCRUDActions: typeof explicitActions =
475
+ metadata.enableCRUDActions
476
+ ? [
477
+ {
478
+ key: 'view',
479
+ name: 'view',
480
+ label: t ? t('datatable.view_record') : 'Ver',
481
+ icon: 'Eye',
482
+ } as any,
483
+ {
484
+ key: 'edit',
485
+ name: 'edit',
486
+ label: t ? t('datatable.edit') : 'Editar',
487
+ icon: 'Pencil',
488
+ } as any,
489
+ {
490
+ key: 'delete',
491
+ name: 'delete',
492
+ label: t ? t('datatable.delete') : 'Eliminar',
493
+ icon: 'Trash2',
494
+ } as any,
495
+ ]
496
+ : []
497
+ const resolvedActions = hasExplicitActions ? explicitActions : defaultCRUDActions
498
+
499
+ if (resolvedActions.length > 0) {
465
500
  columns.push({
466
501
  id: 'actions',
467
502
  header: () => <div className="text-right">{t ? t('common.actions') : 'Acciones'}</div>,
@@ -478,7 +513,7 @@ export function makeDefaultGetDynamicColumns(
478
513
  </Button>
479
514
  </DropdownMenuTrigger>
480
515
  <DropdownMenuContent align="end">
481
- {metadata.actions
516
+ {resolvedActions
482
517
  .filter((action) => {
483
518
  if (!action.condition) return true
484
519
  const { field, operator, value } = action.condition
@@ -156,7 +156,11 @@ export function DynamicCRUDPage(props: DynamicCRUDPageProps) {
156
156
  const effectiveHideCreate = hideCreate || ext?.hideCreate
157
157
  const effectiveHideExport = hideExport || ext?.hideExport
158
158
  const effectiveHideImport = hideImport || ext?.hideImport
159
- const effectiveHideRefresh = hideRefresh || ext?.hideRefresh
159
+ // Refresh defaults to hidden in the page header — <DynamicTable> ships
160
+ // its own refresh icon next to the View / column-visibility toolbar, so
161
+ // showing one in the page chrome is just visual duplication. Apps that
162
+ // want it back can pass `hideRefresh={false}`.
163
+ const effectiveHideRefresh = hideRefresh ?? ext?.hideRefresh ?? true
160
164
  const showCreate = enableCRUD && !effectiveHideCreate
161
165
  const showImport = enableCRUD && !effectiveHideImport
162
166
  const showExport = !effectiveHideExport
@@ -507,7 +507,17 @@ export function DynamicTable({
507
507
  if (!c.filterable || map.has(c.key)) continue
508
508
  const hasStaticOptions = (c.options?.length ?? 0) > 0
509
509
  const hasEndpoint = !!c.searchEndpoint
510
- if (!hasStaticOptions && !hasEndpoint && c.type !== 'boolean') continue
510
+ // Pick the filter UI from column type:
511
+ // - explicit options or searchEndpoint → multi-select dropdown
512
+ // - boolean → boolean toggle (renders as select under the hood)
513
+ // - number / number_range / numeric → number range
514
+ // - everything else (text, email, phone, tags…) → text contains
515
+ let filterType: ColumnFilterConfig['filterType'] = 'select'
516
+ if (hasStaticOptions || hasEndpoint) filterType = 'select'
517
+ else if (c.type === 'boolean') filterType = 'boolean'
518
+ else if (c.type === 'number') filterType = 'number_range'
519
+ else filterType = 'text'
520
+
511
521
  const options = hasStaticOptions
512
522
  ? c.options!.map(o => ({
513
523
  label: o.label,
@@ -519,7 +529,7 @@ export function DynamicTable({
519
529
  ? filterOptionsMap.get(c.searchEndpoint!) || []
520
530
  : []
521
531
  map.set(c.key, {
522
- filterType: 'select',
532
+ filterType,
523
533
  filterKey: c.key,
524
534
  options,
525
535
  selectedValues: dynamicFilters[c.key] || [],