@asteby/metacore-runtime-react 48.0.0 → 49.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 +41 -0
- package/dist/action-modal-dispatcher.d.ts +33 -3
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +84 -11
- package/dist/action-priority.d.ts +34 -0
- package/dist/action-priority.d.ts.map +1 -0
- package/dist/action-priority.js +60 -0
- package/dist/business/catalog-product-search.d.ts +7 -0
- package/dist/business/catalog-product-search.d.ts.map +1 -1
- package/dist/business/catalog-product-search.js +14 -0
- package/dist/business/document-editor-model.d.ts +14 -2
- package/dist/business/document-editor-model.d.ts.map +1 -1
- package/dist/business/document-editor-model.js +42 -4
- package/dist/business/document-editor.d.ts.map +1 -1
- package/dist/business/document-editor.js +68 -19
- package/dist/business/line-items.d.ts +9 -1
- package/dist/business/line-items.d.ts.map +1 -1
- package/dist/business/line-items.js +8 -1
- package/dist/calendar-date.d.ts +12 -0
- package/dist/calendar-date.d.ts.map +1 -1
- package/dist/calendar-date.js +36 -0
- package/dist/document/document-page.d.ts.map +1 -1
- package/dist/document/document-page.js +8 -1
- package/dist/document/evaluate.d.ts.map +1 -1
- package/dist/document/evaluate.js +17 -1
- package/dist/document/index.d.ts +1 -0
- package/dist/document/index.d.ts.map +1 -1
- package/dist/document/index.js +1 -0
- package/dist/document/secondary-bar.d.ts +20 -0
- package/dist/document/secondary-bar.d.ts.map +1 -0
- package/dist/document/secondary-bar.js +44 -0
- package/dist/document/types.d.ts +8 -0
- package/dist/document/types.d.ts.map +1 -1
- package/dist/document-form-dialog.d.ts +23 -2
- package/dist/document-form-dialog.d.ts.map +1 -1
- package/dist/document-form-dialog.js +48 -9
- package/dist/dynamic-columns.d.ts +1 -11
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +7 -17
- package/dist/dynamic-crud-page.d.ts +19 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +6 -5
- package/dist/dynamic-row-actions.d.ts +8 -0
- package/dist/dynamic-row-actions.d.ts.map +1 -1
- package/dist/dynamic-row-actions.js +12 -2
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +64 -63
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/primitives/contributions.d.ts +14 -3
- package/dist/primitives/contributions.d.ts.map +1 -1
- package/dist/primitives/contributions.js +50 -13
- package/dist/record-actions.d.ts +21 -0
- package/dist/record-actions.d.ts.map +1 -0
- package/dist/record-actions.js +76 -0
- package/dist/row-actions-menu.d.ts +32 -0
- package/dist/row-actions-menu.d.ts.map +1 -0
- package/dist/row-actions-menu.js +59 -0
- package/dist/slot.d.ts +4 -21
- package/dist/slot.d.ts.map +1 -1
- package/dist/slot.js +14 -36
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/document-editor-model.test.ts +55 -1
- package/src/__tests__/document-editor.test.tsx +80 -1
- package/src/__tests__/document-form-create-model.test.tsx +143 -0
- package/src/__tests__/document-form-dialog.test.tsx +40 -0
- package/src/__tests__/field-default-today.test.tsx +116 -0
- package/src/__tests__/prefill-scalar-from-record.test.ts +52 -1
- package/src/__tests__/row-action-link-navigation.test.ts +17 -0
- package/src/__tests__/secondary-actions.test.tsx +261 -0
- package/src/action-modal-dispatcher.tsx +97 -17
- package/src/action-priority.ts +82 -0
- package/src/business/business.test.ts +1 -1
- package/src/business/catalog-product-search.ts +18 -0
- package/src/business/document-editor-model.ts +48 -6
- package/src/business/document-editor.tsx +75 -20
- package/src/business/line-items.ts +16 -1
- package/src/calendar-date.ts +36 -0
- package/src/document/document-page.tsx +15 -0
- package/src/document/evaluate.ts +17 -1
- package/src/document/index.ts +1 -0
- package/src/document/secondary-bar.tsx +92 -0
- package/src/document/types.ts +8 -0
- package/src/document-form-dialog.tsx +67 -8
- package/src/dynamic-columns.tsx +13 -57
- package/src/dynamic-crud-page.tsx +31 -4
- package/src/dynamic-row-actions.tsx +12 -2
- package/src/dynamic-table.tsx +3 -0
- package/src/index.ts +31 -0
- package/src/primitives/contributions.ts +59 -12
- package/src/record-actions.ts +101 -0
- package/src/row-actions-menu.tsx +159 -0
- package/src/slot.tsx +18 -53
- package/src/types.ts +35 -0
|
@@ -16,7 +16,7 @@ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogH
|
|
|
16
16
|
import { Loader2 } from 'lucide-react';
|
|
17
17
|
import { toast } from 'sonner';
|
|
18
18
|
import { useApi } from './api-context';
|
|
19
|
-
import { useOrgTaxRate } from './org-runtime-context';
|
|
19
|
+
import { useOrgTaxRate, useTimeZone } from './org-runtime-context';
|
|
20
20
|
import { DynamicIcon } from './dynamic-icon';
|
|
21
21
|
import { FieldCell, FieldGrid, FieldLabel } from './field-grid';
|
|
22
22
|
import { FormErrorBanner } from './business/feedback';
|
|
@@ -28,9 +28,28 @@ import { validateValues, bagHasErrors } from './validator';
|
|
|
28
28
|
import { clearFieldErrorTree } from './field-validation-ui';
|
|
29
29
|
import { emitRecordMutation } from './record-mutation-events';
|
|
30
30
|
import { isLineItemsField, resolveWidget, scopeValueFromFilterToken } from './dynamic-form-schema';
|
|
31
|
-
import { createCatalogProductSearch } from './business/catalog-product-search';
|
|
31
|
+
import { createCatalogProductSearch, withDefaultTaxRate } from './business/catalog-product-search';
|
|
32
32
|
import { DocumentEditor } from './business/document-editor';
|
|
33
33
|
import { editorLinesConfig, isEditorLayout } from './business/document-editor-model';
|
|
34
|
+
/**
|
|
35
|
+
* Si «Crear» de estos formularios es un alta delegada: un único tipo (p. ej.
|
|
36
|
+
* ya acotado por el filtro de la vista) con `create_model`. Con varios tipos el
|
|
37
|
+
* selector la ofrece como una tarjeta más.
|
|
38
|
+
*/
|
|
39
|
+
export function delegatedCreate(forms) {
|
|
40
|
+
const only = forms?.types.length === 1 ? forms.types[0] : undefined;
|
|
41
|
+
const model = only?.create_model?.trim();
|
|
42
|
+
return only && model ? { model, type: only } : undefined;
|
|
43
|
+
}
|
|
44
|
+
/** Quita los tipos delegados cuando el host no sabe navegar a otro modelo. */
|
|
45
|
+
export function withoutDelegatedTypes(forms) {
|
|
46
|
+
if (!forms)
|
|
47
|
+
return forms;
|
|
48
|
+
const types = forms.types.filter((x) => !x.create_model?.trim());
|
|
49
|
+
if (types.length === forms.types.length)
|
|
50
|
+
return forms;
|
|
51
|
+
return types.length > 0 ? { ...forms, types } : undefined;
|
|
52
|
+
}
|
|
34
53
|
/** Manifest efectivo: la prop gana sobre el metadata; sin tipos devuelve undefined. */
|
|
35
54
|
export function resolveDocumentForms(metadata, override) {
|
|
36
55
|
const m = override ?? metadata?.document_forms;
|
|
@@ -56,11 +75,13 @@ export function scopeDocumentFormsToFilter(forms, filter) {
|
|
|
56
75
|
return { forms: undefined, hideCreate: true };
|
|
57
76
|
return { forms: types.length === forms.types.length ? forms : { ...forms, types }, hideCreate: false };
|
|
58
77
|
}
|
|
59
|
-
export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms, initialType, onSaved, searchProducts, productModel, defaultTaxRate, currency, record, initialSource, }) {
|
|
78
|
+
export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms, initialType, onSaved, searchProducts, productModel, defaultTaxRate, currency, record, initialSource, onDelegateCreate, }) {
|
|
60
79
|
const { t, i18n } = useTranslation();
|
|
61
80
|
const api = useApi();
|
|
62
81
|
const orgTaxRate = useOrgTaxRate();
|
|
63
|
-
const
|
|
82
|
+
const orgTimeZone = useTimeZone();
|
|
83
|
+
// Sin onDelegateCreate el host no sabe abrir otro modelo: sus tipos no se ofrecen.
|
|
84
|
+
const types = useMemo(() => (onDelegateCreate ? forms.types : forms.types.filter((x) => !x.create_model?.trim())), [forms.types, onDelegateCreate]);
|
|
64
85
|
const single = types.length === 1 ? types[0] : undefined;
|
|
65
86
|
const [typeKey, setTypeKey] = useState(initialType ?? single?.key ?? null);
|
|
66
87
|
const [step, setStep] = useState(initialType || single ? 'fields' : 'type');
|
|
@@ -71,11 +92,24 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
71
92
|
const [formError, setFormError] = useState();
|
|
72
93
|
const [saving, setSaving] = useState(false);
|
|
73
94
|
const type = useMemo(() => types.find((x) => x.key === typeKey) ?? null, [types, typeKey]);
|
|
95
|
+
const delegatedModel = type?.create_model?.trim() || undefined;
|
|
96
|
+
// Tipo con alta en otro modelo: no hay formulario aquí; el host navega.
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!open || !type || !delegatedModel)
|
|
99
|
+
return;
|
|
100
|
+
onDelegateCreate?.({ model: delegatedModel, type });
|
|
101
|
+
onOpenChange(false);
|
|
102
|
+
// onDelegateCreate/onOpenChange: callbacks del host, no disparan de nuevo.
|
|
103
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
}, [open, type, delegatedModel]);
|
|
74
105
|
// Sin buscador inyectado, el paso de renglones solo ofrecía «Renglón libre»
|
|
75
106
|
// y la línea entraba sin precio (DynamicCRUDPage no pasa searchProducts).
|
|
76
|
-
// Default: el catálogo de productos de la org.
|
|
107
|
+
// Default: el catálogo de productos de la org. El buscador del host también
|
|
108
|
+
// recibe el IVA de la org para los productos que no traen su tasa.
|
|
77
109
|
const taxRate = defaultTaxRate ?? orgTaxRate;
|
|
78
|
-
const lineSearch = useMemo(() => searchProducts
|
|
110
|
+
const lineSearch = useMemo(() => searchProducts
|
|
111
|
+
? withDefaultTaxRate(searchProducts, taxRate)
|
|
112
|
+
: createCatalogProductSearch(api, { model: productModel, defaultTaxRate: taxRate }), [searchProducts, api, productModel, taxRate]);
|
|
79
113
|
const lineCfg = type ? editorLinesConfig(type, forms) : undefined;
|
|
80
114
|
// Reset every time the dialog (re)opens.
|
|
81
115
|
useEffect(() => {
|
|
@@ -93,10 +127,13 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
93
127
|
useEffect(() => {
|
|
94
128
|
if (!type)
|
|
95
129
|
return;
|
|
96
|
-
setFormData({
|
|
130
|
+
setFormData({
|
|
131
|
+
...buildFieldDefaults(type.fields, record, undefined, { timeZone: orgTimeZone }),
|
|
132
|
+
...(type.defaults ?? {}),
|
|
133
|
+
});
|
|
97
134
|
setFieldErrors({});
|
|
98
135
|
setFormError(undefined);
|
|
99
|
-
}, [type, record]);
|
|
136
|
+
}, [type, record, orgTimeZone]);
|
|
100
137
|
const updateField = (key, value) => {
|
|
101
138
|
setFormData((prev) => ({ ...prev, [key]: value }));
|
|
102
139
|
setFieldErrors((prev) => clearFieldErrorTree(prev, key));
|
|
@@ -199,9 +236,11 @@ export function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms,
|
|
|
199
236
|
: s === 'fields'
|
|
200
237
|
? t('documentForm.step_data', { defaultValue: 'Datos' })
|
|
201
238
|
: tl(lineCfg?.title ?? 'Renglones');
|
|
239
|
+
if (open && type && delegatedModel)
|
|
240
|
+
return null;
|
|
202
241
|
// layout "editor": una sola pantalla por secciones (DocumentEditor). Sin él
|
|
203
242
|
// sigue el wizard de siempre (retrocompatible).
|
|
204
|
-
if (open && type && step !== 'type' && isEditorLayout(type)) {
|
|
243
|
+
if (open && type && step !== 'type' && (isEditorLayout(type) || !!initialSource)) {
|
|
205
244
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex max-h-[92dvh] flex-col overflow-hidden", style: { maxHeight: '92dvh', maxWidth: '960px', width: '95vw' }, "data-slot": "document-editor-dialog", children: [_jsxs(DialogHeader, { className: "shrink-0", children: [_jsx(DialogTitle, { children: tl(type.label) }), type.description && _jsx(DialogDescription, { children: tl(type.description) })] }), _jsx(DocumentEditor, { model: model, endpoint: endpoint, forms: forms, type: type, record: record, initialSource: initialSource, searchProducts: lineSearch, currency: currency, onCancel: () => (canGoBackToTypes ? setStep('type') : onOpenChange(false)), onSaved: (rec) => {
|
|
206
245
|
onSaved?.(rec);
|
|
207
246
|
onOpenChange(false);
|
|
@@ -38,17 +38,7 @@ export declare const aggregateOf: (col: ColumnDefinition) => string | undefined;
|
|
|
38
38
|
* empty filtered set reads cleanly.
|
|
39
39
|
*/
|
|
40
40
|
export declare const formatAggregateTotal: (col: ColumnDefinition, value: unknown, currency?: string, locale?: string) => string;
|
|
41
|
-
|
|
42
|
-
* One entry of the row "…" menu. An action gated by a missing optional addon
|
|
43
|
-
* (`requiresAddon`) stays listed with a lock + tooltip; the click still goes to
|
|
44
|
-
* `onAction`, whose shared handler (useDynamicRowActions) opens the
|
|
45
|
-
* requires-addon dialog instead of executing.
|
|
46
|
-
*/
|
|
47
|
-
export declare function RowActionMenuItem({ action, label, onSelect, }: {
|
|
48
|
-
action: any;
|
|
49
|
-
label: React.ReactNode;
|
|
50
|
-
onSelect: (e: React.MouseEvent) => void;
|
|
51
|
-
}): React.JSX.Element;
|
|
41
|
+
export { RowActionMenuItem } from './row-actions-menu';
|
|
52
42
|
/**
|
|
53
43
|
* State-machine gate for per-row actions.
|
|
54
44
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAwC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,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;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AA2CD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAwBtD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,EAAE,aAAa,MAAM,KAAG,OAMvF,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OA0C5D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,EAAE,aAAa,MAAM,KAAG,OACY,CAAA;AAE5F;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,GAAG,KAAG,MAAM,GAAG,SACmD,CAAA;AAkHhH;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiD5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,GACjE,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOxE;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GACzB,KAAK,gBAAgB,EACrB,KAAK,GAAG,EACR,OAAO,GAAG,EACV,mBAAe,KAChB,MAAM,GAAG,SAcX,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOzE,CAAA;AAgKD;;;;GAIG;AACH;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB,CAgCA,CAAA;AAED,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAsqBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -16,8 +16,8 @@ import * as React from 'react';
|
|
|
16
16
|
import { format } from 'date-fns';
|
|
17
17
|
import { isCalendarDayValue, parseCalendarDate } from './calendar-date';
|
|
18
18
|
import { es, enUS } from 'date-fns/locale';
|
|
19
|
-
import { Calendar, Check, Copy, Mail, Minus
|
|
20
|
-
import { Avatar, AvatarFallback, AvatarImage, Badge,
|
|
19
|
+
import { Calendar, Check, Copy, Mail, Minus } from 'lucide-react';
|
|
20
|
+
import { Avatar, AvatarFallback, AvatarImage, Badge, Checkbox, InitialsAvatar, } from '@asteby/metacore-ui';
|
|
21
21
|
import { DataTableColumnHeader, FilterableColumnHeader, } from '@asteby/metacore-ui/data-table';
|
|
22
22
|
import { generateBadgeStyles, getInitials, relationChipStyles, } from '@asteby/metacore-ui/lib';
|
|
23
23
|
import { Progress } from './dialogs/_primitives';
|
|
@@ -30,7 +30,7 @@ import { DynamicIcon, isLucideIconName } from './dynamic-icon';
|
|
|
30
30
|
import { CollectionCell } from './collection-cell';
|
|
31
31
|
import { isNilUuid, normalizeNilUuid } from './nil-uuid';
|
|
32
32
|
import { useOptionsResolver } from './use-options-resolver';
|
|
33
|
-
import {
|
|
33
|
+
import { RowActionsMenu } from './row-actions-menu';
|
|
34
34
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
35
35
|
const defaultGetImageUrl = (path) => path;
|
|
36
36
|
const getNestedValue = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
@@ -109,17 +109,9 @@ const CodeCell = ({ text, maxLength }) => {
|
|
|
109
109
|
};
|
|
110
110
|
return (_jsxs("div", { className: "group flex items-center gap-1.5", children: [_jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/80", title: text, children: display }), _jsx("button", { type: "button", onClick: onCopy, className: "opacity-0 transition-opacity group-hover:opacity-100 text-muted-foreground hover:text-foreground", "aria-label": "Copiar", title: "Copiar", children: copied ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-500" })) : (_jsx(Copy, { className: "h-3.5 w-3.5" })) })] }));
|
|
111
111
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
* `onAction`, whose shared handler (useDynamicRowActions) opens the
|
|
116
|
-
* requires-addon dialog instead of executing.
|
|
117
|
-
*/
|
|
118
|
-
export function RowActionMenuItem({ action, label, onSelect, }) {
|
|
119
|
-
const requirement = resolveRequiresAddon(action);
|
|
120
|
-
const requiresAddonLabel = useRequiresAddonLabel();
|
|
121
|
-
return (_jsxs(DropdownMenuItem, { onClick: onSelect, title: requirement ? requiresAddonLabel(requirement) : undefined, "data-requires-addon": requirement?.key, children: [_jsx(DynamicIcon, { name: action.icon || 'Zap', className: "mr-2 h-4 w-4" }), label, requirement && _jsx(RequiresAddonLock, { requirement: requirement })] }));
|
|
122
|
-
}
|
|
112
|
+
// RowActionMenuItem vive en row-actions-menu.tsx (junto al menú que lo usa);
|
|
113
|
+
// se re-exporta aquí por compatibilidad (dynamic-kanban, hosts).
|
|
114
|
+
export { RowActionMenuItem } from './row-actions-menu';
|
|
123
115
|
/**
|
|
124
116
|
* Lifecycle column used by `requiresState`: prefer `status` (workshop, vehicles,
|
|
125
117
|
* …) and fall back to `state` (purchases, inventory transfers, …). Empty string
|
|
@@ -1049,9 +1041,7 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
|
|
|
1049
1041
|
size: 80,
|
|
1050
1042
|
maxSize: 80,
|
|
1051
1043
|
meta: {},
|
|
1052
|
-
cell: ({ row }) => (_jsx(
|
|
1053
|
-
.filter((action) => isRowActionVisible(action, row.original, lifecycleStageField(metadata)))
|
|
1054
|
-
.map((action) => (_jsx(RowActionMenuItem, { action: action, label: translateMetadataLabel(action.label, t), onSelect: () => onAction && onAction(action.key, row.original) }, action.key))) })] }) })),
|
|
1044
|
+
cell: ({ row }) => (_jsx(RowActionsMenu, { actions: resolvedActions.filter((action) => isRowActionVisible(action, row.original, lifecycleStageField(metadata))), row: row.original, onAction: onAction })),
|
|
1055
1045
|
});
|
|
1056
1046
|
}
|
|
1057
1047
|
return columns;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { type DelegatedCreate } from './document-form-dialog';
|
|
2
3
|
import type { DocumentFormsManifest } from './types';
|
|
3
4
|
export interface DynamicCRUDPageStrings {
|
|
4
5
|
refresh?: string;
|
|
@@ -51,6 +52,24 @@ export interface DynamicCRUDPageProps {
|
|
|
51
52
|
* formulario para él.
|
|
52
53
|
*/
|
|
53
54
|
filter?: Record<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Abre el alta guiada al montar («crear desde»): tipo de documento y origen
|
|
57
|
+
* a precargar (`sources[].key` + id), p. ej. leídos de la URL
|
|
58
|
+
* `?create=<tipo>&from=<fuente>&from_id=<id>`. Solo con document_forms.
|
|
59
|
+
*/
|
|
60
|
+
initialCreate?: {
|
|
61
|
+
type?: string;
|
|
62
|
+
source?: {
|
|
63
|
+
key: string;
|
|
64
|
+
id: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Alta delegada (`document_forms.types[].create_model`): el host abre la
|
|
69
|
+
* página del modelo destino con su alta abierta. Sin él, esos tipos no se
|
|
70
|
+
* ofrecen (y si la vista queda acotada a uno, no hay «Crear»).
|
|
71
|
+
*/
|
|
72
|
+
onDelegateCreate?: (target: DelegatedCreate) => void;
|
|
54
73
|
}
|
|
55
74
|
/**
|
|
56
75
|
* Page-level CRUD shell wired around <DynamicTable>. Hosts mount a single
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAMd,OAAO,EAKH,KAAK,eAAe,EACvB,MAAM,wBAAwB,CAAA;AAO/B,OAAO,KAAK,EAAE,qBAAqB,EAAiB,MAAM,SAAS,CAAA;AAEnE,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;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;IACvE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAA;CACvD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,qBAmP1D"}
|
|
@@ -29,7 +29,7 @@ import { useApi } from './api-context';
|
|
|
29
29
|
import { useMetadataCache } from './metadata-cache';
|
|
30
30
|
import { DynamicTable } from './dynamic-table';
|
|
31
31
|
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
32
|
-
import { DocumentFormDialog, resolveDocumentForms, scopeDocumentFormsToFilter } from './document-form-dialog';
|
|
32
|
+
import { DocumentFormDialog, resolveDocumentForms, scopeDocumentFormsToFilter, withoutDelegatedTypes, } from './document-form-dialog';
|
|
33
33
|
import { ExportDialog } from './dialogs/export';
|
|
34
34
|
import { ImportDialog } from './dialogs/import';
|
|
35
35
|
import { getModelExtension } from './model-extension-registry';
|
|
@@ -48,7 +48,7 @@ const defaultStrings = {
|
|
|
48
48
|
* metadata fetch, dialogs and toolbar.
|
|
49
49
|
*/
|
|
50
50
|
export function DynamicCRUDPage(props) {
|
|
51
|
-
const { model, endpoint, title: titleOverride, newLabel, i18n, hideCreate, hideExport, hideImport, hideRefresh, headerExtras, toolbarExtras, classes, onChange, documentForms, filter, } = props;
|
|
51
|
+
const { model, endpoint, title: titleOverride, newLabel, i18n, hideCreate, hideExport, hideImport, hideRefresh, headerExtras, toolbarExtras, classes, onChange, documentForms, filter, initialCreate, onDelegateCreate, } = props;
|
|
52
52
|
const strings = { ...defaultStrings, ...(i18n ?? {}) };
|
|
53
53
|
const dataEndpoint = endpoint ?? `/dynamic/${model}`;
|
|
54
54
|
const ext = getModelExtension(model);
|
|
@@ -56,7 +56,7 @@ export function DynamicCRUDPage(props) {
|
|
|
56
56
|
const cachedMeta = useMetadataCache((s) => s.getMetadata(model));
|
|
57
57
|
const [metadata, setMetadata] = useState(cachedMeta ?? null);
|
|
58
58
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
59
|
-
const [openCreate, setOpenCreate] = useState(
|
|
59
|
+
const [openCreate, setOpenCreate] = useState(!!initialCreate);
|
|
60
60
|
const [openExport, setOpenExport] = useState(false);
|
|
61
61
|
const [openImport, setOpenImport] = useState(false);
|
|
62
62
|
useEffect(() => {
|
|
@@ -114,7 +114,8 @@ export function DynamicCRUDPage(props) {
|
|
|
114
114
|
// (useCan defaults to always-true), in which case create/export/import
|
|
115
115
|
// require `lowercase(model).create|export|import`.
|
|
116
116
|
const can = useCan();
|
|
117
|
-
const
|
|
117
|
+
const declaredForms = resolveDocumentForms(metadata, documentForms);
|
|
118
|
+
const scoped = scopeDocumentFormsToFilter(onDelegateCreate ? declaredForms : withoutDelegatedTypes(declaredForms), filter);
|
|
118
119
|
const guidedForms = scoped.forms;
|
|
119
120
|
const showCreate = primary.showGenericCreate && !scoped.hideCreate && can(modelCapability(model, 'create'));
|
|
120
121
|
const showImport = enableCRUD && !effectiveHideImport && can(modelCapability(model, 'import'));
|
|
@@ -138,5 +139,5 @@ export function DynamicCRUDPage(props) {
|
|
|
138
139
|
const titleCls = classes?.title ?? 'text-2xl font-bold tracking-tight';
|
|
139
140
|
const toolbarCls = classes?.toolbar ?? 'flex items-center gap-2';
|
|
140
141
|
const tableWrapperCls = classes?.tableWrapper ?? 'flex-1 min-h-0';
|
|
141
|
-
return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, _jsx(ModelActionToolbar, { model: model, endpoint: dataEndpoint, actions: metadata?.actions, primaryActionKey: ext?.primaryActionKey, onChange: handleRefresh }), showCreate && (_jsxs("button", { type: 'button', "data-primary": 'true', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey, defaultFilters: tableFilters }, model) })] }), showCreate && guidedForms && (_jsx(DocumentFormDialog, { open: openCreate, onOpenChange: setOpenCreate, model: model, endpoint: dataEndpoint, forms: guidedForms, onSaved: handleRefresh })), showCreate && !guidedForms && (_jsx(DynamicRecordDialog, { open: openCreate, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
|
|
142
|
+
return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, _jsx(ModelActionToolbar, { model: model, endpoint: dataEndpoint, actions: metadata?.actions, primaryActionKey: ext?.primaryActionKey, onChange: handleRefresh }), showCreate && (_jsxs("button", { type: 'button', "data-primary": 'true', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey, defaultFilters: tableFilters }, model) })] }), showCreate && guidedForms && (_jsx(DocumentFormDialog, { open: openCreate, onOpenChange: setOpenCreate, model: model, endpoint: dataEndpoint, forms: guidedForms, initialType: initialCreate?.type, initialSource: initialCreate?.source, onSaved: handleRefresh, onDelegateCreate: onDelegateCreate })), showCreate && !guidedForms && (_jsx(DynamicRecordDialog, { open: openCreate && !initialCreate?.source, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
|
|
142
143
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { TableMetadata } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Destino de una acción link. La query (`?create=…&from_id=…`) va como `search`:
|
|
4
|
+
* metida en `to` el router la tomaba como parte del path y la ruta no casaba.
|
|
5
|
+
*/
|
|
6
|
+
export declare function linkNavigation(url: string): {
|
|
7
|
+
to: string;
|
|
8
|
+
search?: Record<string, string>;
|
|
9
|
+
};
|
|
2
10
|
export interface UseDynamicRowActionsParams {
|
|
3
11
|
/** Model key as registered on the backend (e.g. "issue"). */
|
|
4
12
|
model: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-row-actions.d.ts","sourceRoot":"","sources":["../src/dynamic-row-actions.tsx"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,SAAS,CAAA;AAE3E,MAAM,WAAW,0BAA0B;IACvC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,iBAAiB;IAC9B,+CAA+C;IAC/C,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE;;;OAGG;IACH,OAAO,EAAE,KAAK,CAAC,YAAY,CAAA;CAC9B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,SAAS,GACZ,EAAE,0BAA0B,GAAG,iBAAiB,CA6IhD"}
|
|
1
|
+
{"version":3,"file":"dynamic-row-actions.d.ts","sourceRoot":"","sources":["../src/dynamic-row-actions.tsx"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,SAAS,CAAA;AAE3E;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAI3F;AAED,MAAM,WAAW,0BAA0B;IACvC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,iBAAiB;IAC9B,+CAA+C;IAC/C,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE;;;OAGG;IACH,OAAO,EAAE,KAAK,CAAC,YAAY,CAAA;CAC9B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,SAAS,GACZ,EAAE,0BAA0B,GAAG,iBAAiB,CA6IhD"}
|
|
@@ -30,6 +30,16 @@ import { useReasonPrompt } from './reason-prompt';
|
|
|
30
30
|
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
31
31
|
import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
32
32
|
import { RequiresAddonDialog, resolveRequiresAddon } from './requires-addon';
|
|
33
|
+
/**
|
|
34
|
+
* Destino de una acción link. La query (`?create=…&from_id=…`) va como `search`:
|
|
35
|
+
* metida en `to` el router la tomaba como parte del path y la ruta no casaba.
|
|
36
|
+
*/
|
|
37
|
+
export function linkNavigation(url) {
|
|
38
|
+
const i = url.indexOf('?');
|
|
39
|
+
if (i < 0)
|
|
40
|
+
return { to: url };
|
|
41
|
+
return { to: url.slice(0, i), search: Object.fromEntries(new URLSearchParams(url.slice(i + 1))) };
|
|
42
|
+
}
|
|
33
43
|
/**
|
|
34
44
|
* Owns the state + dialogs + dispatch logic for per-row actions, shared by
|
|
35
45
|
* DynamicTable and DynamicKanban so both behave identically.
|
|
@@ -68,8 +78,8 @@ export function useDynamicRowActions({ model, endpoint, mutationEndpoint, metada
|
|
|
68
78
|
}
|
|
69
79
|
const linkDef = metadata?.actions?.find((a) => a.key === action && a.type === 'link');
|
|
70
80
|
if (linkDef?.linkUrl) {
|
|
71
|
-
const url = linkDef.linkUrl.replace(/\{(\w+)\}/g, (_, field) => String(row[field] ?? ''));
|
|
72
|
-
navigate(
|
|
81
|
+
const url = linkDef.linkUrl.replace(/\{(\w+)\}/g, (_, field) => encodeURIComponent(String(row[field] ?? '')));
|
|
82
|
+
navigate(linkNavigation(url));
|
|
73
83
|
return;
|
|
74
84
|
}
|
|
75
85
|
const actionDef = metadata?.actions?.find((a) => a.key === action);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAmC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAmC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAyGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,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;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CACpC;AAQD,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,EAC1C,cAAc,GACjB,EAAE,iBAAiB,+BA2kDnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -34,6 +34,7 @@ import { translateOptionLabels } from './filter-chips';
|
|
|
34
34
|
import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll';
|
|
35
35
|
import { DYNAMIC_TABLE_CARD_ESTIMATE_PX, DYNAMIC_TABLE_ROW_ESTIMATE_PX, resolveVirtualizeThreshold, } from './table-virtualization';
|
|
36
36
|
import { OptionsContext } from './options-context';
|
|
37
|
+
import { RowActionsModelContext } from './row-actions-menu';
|
|
37
38
|
import { getSearchableColumnKeys } from './column-visibility';
|
|
38
39
|
import { visibleRelationInclude } from './list-include';
|
|
39
40
|
import { useDebouncedValue } from './use-debounced-value';
|
|
@@ -1274,76 +1275,76 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
|
|
|
1274
1275
|
if (!metadata) {
|
|
1275
1276
|
return _jsx("div", { className: "text-center text-muted-foreground py-8", children: "Error al cargar la configuraci\u00F3n de la tabla." });
|
|
1276
1277
|
}
|
|
1277
|
-
return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [exportEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), importEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsxs("div", { ref: infDesktopRoot, className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: [_jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1278
|
+
return (_jsx(RowActionsModelContext.Provider, { value: model, children: _jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [exportEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), importEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsxs("div", { ref: infDesktopRoot, className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: [_jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
|
|
1279
|
+
const isActionsColumn = header.id === 'actions';
|
|
1280
|
+
return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
1281
|
+
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : tableRows.length ? (_jsxs(_Fragment, { children: [shouldVirtualize ? (_jsxs(_Fragment, { children: [desktopVirtualizer.getVirtualItems().length > 0 && (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', "aria-hidden": true, children: _jsx(TableCell, { colSpan: colSpan, className: 'p-0', style: {
|
|
1282
|
+
height: desktopVirtualizer.getVirtualItems()[0]?.start ?? 0,
|
|
1283
|
+
} }) })), desktopVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
1284
|
+
const row = tableRows[virtualRow.index];
|
|
1285
|
+
if (!row)
|
|
1286
|
+
return null;
|
|
1287
|
+
return (_jsx(TableRow, { "data-index": virtualRow.index, ref: desktopVirtualizer.measureElement, "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
|
|
1288
|
+
const isActionsColumn = cell.column.id === 'actions';
|
|
1289
|
+
const isSelectColumn = cell.column.id === 'select';
|
|
1290
|
+
return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), onClick: (isActionsColumn || isSelectColumn) ? (e) => e.stopPropagation() : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
1291
|
+
}) }, row.id));
|
|
1292
|
+
}), (() => {
|
|
1293
|
+
const items = desktopVirtualizer.getVirtualItems();
|
|
1294
|
+
const last = items[items.length - 1];
|
|
1295
|
+
const pad = last != null
|
|
1296
|
+
? desktopVirtualizer.getTotalSize() - last.end
|
|
1297
|
+
: 0;
|
|
1298
|
+
if (pad <= 0)
|
|
1299
|
+
return null;
|
|
1300
|
+
return (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', "aria-hidden": true, children: _jsx(TableCell, { colSpan: colSpan, className: 'p-0', style: { height: pad } }) }));
|
|
1301
|
+
})()] })) : (tableRows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
|
|
1302
|
+
const isActionsColumn = cell.column.id === 'actions';
|
|
1303
|
+
const isSelectColumn = cell.column.id === 'select';
|
|
1304
|
+
return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), onClick: (isActionsColumn || isSelectColumn) ? (e) => e.stopPropagation() : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
1305
|
+
}) }, row.id)))), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0' }) }))] })) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsx("div", { className: "flex flex-col items-center gap-1", children: forbidden ? (_jsxs(_Fragment, { children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", role: "alert", children: t('dynamic.forbidden_title', { defaultValue: 'Sin permiso para ver este módulo' }) }), _jsx("p", { className: "text-sm text-muted-foreground", children: t('dynamic.forbidden_hint', { defaultValue: 'Tu rol no tiene acceso de lectura a estos datos. Pide acceso a un administrador.' }) })] })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })) })] }) }) })) }), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableFooter, { className: "bg-transparent", children: _jsx(TableRow, { className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((leaf, idx) => {
|
|
1306
|
+
const col = (metadata?.columns ?? []).find((c) => c.key === leaf.id);
|
|
1307
|
+
const isFirst = idx === 0;
|
|
1308
|
+
const stickyBase = 'sticky bottom-0 z-10 border-t bg-background py-2 font-semibold';
|
|
1309
|
+
// Aggregate cell: render the SUM formatted like the body cell.
|
|
1310
|
+
if (col && aggregateOf(col)) {
|
|
1311
|
+
return (_jsx(TableCell, { className: `${stickyBase} text-right tabular-nums`, children: formatAggregateTotal(col, footerTotals[leaf.id], currency, i18n.language) }, leaf.id));
|
|
1312
|
+
}
|
|
1313
|
+
// First non-aggregate column carries the "Total" label.
|
|
1314
|
+
return (_jsx(TableCell, { className: stickyBase, children: isFirst ? t('common.total', 'Total') : '' }, leaf.id));
|
|
1315
|
+
}) }) }))] }), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx("div", { className: 'p-2', children: _jsx(Skeleton, { className: 'h-8 w-full', "data-testid": 'table-loading-more' }) })), _jsx("div", { ref: infDesktopSentinel, className: 'h-1 w-full', "aria-hidden": true })] }))] }), _jsxs("div", { ref: infMobileRoot, className: 'flex flex-1 min-h-0 flex-col gap-2 overflow-y-auto sm:hidden', children: [loadingData && data.length === 0 ? (Array.from({ length: 5 }).map((_, i) => (_jsxs("div", { className: 'rounded-lg border bg-card p-3', children: [_jsx(Skeleton, { className: 'h-4 w-24' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-40' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-32' })] }, i)))) : tableRows.length ? (shouldVirtualize ? (_jsx("div", { className: 'relative w-full', style: { height: mobileVirtualizer.getTotalSize() }, children: mobileVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
1316
|
+
const row = tableRows[virtualRow.index];
|
|
1317
|
+
if (!row)
|
|
1318
|
+
return null;
|
|
1319
|
+
const cells = row.getVisibleCells();
|
|
1320
|
+
const actionsCell = cells.find((c) => c.column.id === 'actions');
|
|
1321
|
+
const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
|
|
1322
|
+
return (_jsxs("div", { "data-index": virtualRow.index, ref: mobileVirtualizer.measureElement, "data-state": row.getIsSelected() && 'selected', className: cn('absolute left-0 right-0 flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', onRowClick && 'cursor-pointer'), style: { transform: `translateY(${virtualRow.start}px)` }, onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: [dataCells.map((cell) => {
|
|
1323
|
+
const cellMeta = cell.column.columnDef.meta;
|
|
1324
|
+
const header = cell.column.columnDef.header;
|
|
1325
|
+
const label = cellMeta?.label ??
|
|
1326
|
+
(typeof header === 'string' ? header : cell.column.id);
|
|
1327
|
+
return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 overflow-hidden break-words text-right font-medium [&_[data-slot=badge]]:max-w-full [&_[data-slot=badge]]:whitespace-normal [&_[data-slot=badge]]:text-start', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
|
|
1328
|
+
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
1329
|
+
}) })) : (tableRows.map((row) => {
|
|
1318
1330
|
const cells = row.getVisibleCells();
|
|
1319
1331
|
const actionsCell = cells.find((c) => c.column.id === 'actions');
|
|
1320
1332
|
const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
|
|
1321
|
-
return (_jsxs("div", { "data-
|
|
1333
|
+
return (_jsxs("div", { "data-state": row.getIsSelected() && 'selected', className: cn('flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: [dataCells.map((cell) => {
|
|
1334
|
+
// El label humano vive en columnDef.meta.label (lo
|
|
1335
|
+
// adjunta la fábrica de columnas); el header casi
|
|
1336
|
+
// nunca es string (es un componente), así que caer a
|
|
1337
|
+
// column.id mostraba keys crudas ('user.avatar',
|
|
1338
|
+
// 'created_at') en las cards móviles.
|
|
1322
1339
|
const cellMeta = cell.column.columnDef.meta;
|
|
1323
1340
|
const header = cell.column.columnDef.header;
|
|
1324
1341
|
const label = cellMeta?.label ??
|
|
1325
1342
|
(typeof header === 'string' ? header : cell.column.id);
|
|
1326
1343
|
return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 overflow-hidden break-words text-right font-medium [&_[data-slot=badge]]:max-w-full [&_[data-slot=badge]]:whitespace-normal [&_[data-slot=badge]]:text-start', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
|
|
1327
1344
|
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
1328
|
-
}) })) : (
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
// El label humano vive en columnDef.meta.label (lo
|
|
1334
|
-
// adjunta la fábrica de columnas); el header casi
|
|
1335
|
-
// nunca es string (es un componente), así que caer a
|
|
1336
|
-
// column.id mostraba keys crudas ('user.avatar',
|
|
1337
|
-
// 'created_at') en las cards móviles.
|
|
1338
|
-
const cellMeta = cell.column.columnDef.meta;
|
|
1339
|
-
const header = cell.column.columnDef.header;
|
|
1340
|
-
const label = cellMeta?.label ??
|
|
1341
|
-
(typeof header === 'string' ? header : cell.column.id);
|
|
1342
|
-
return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 overflow-hidden break-words text-right font-medium [&_[data-slot=badge]]:max-w-full [&_[data-slot=badge]]:whitespace-normal [&_[data-slot=badge]]:text-start', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
|
|
1343
|
-
}), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
|
|
1344
|
-
}))) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), forbidden ? (_jsxs(_Fragment, { children: [_jsx("h3", { className: 'text-base font-semibold text-foreground', role: 'alert', children: t('dynamic.forbidden_title', { defaultValue: 'Sin permiso para ver este módulo' }) }), _jsx("p", { className: 'text-sm text-muted-foreground', children: t('dynamic.forbidden_hint', { defaultValue: 'Tu rol no tiene acceso de lectura a estos datos. Pide acceso a un administrador.' }) })] })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] }))] })), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx(Skeleton, { className: 'h-16 w-full shrink-0', "data-testid": 'table-loading-more-mobile' })), _jsx("div", { ref: infMobileSentinel, className: 'h-1 w-full shrink-0', "aria-hidden": true })] }))] }), _jsx("div", { className: 'shrink-0 pt-4', children: infiniteScroll ? (data.length > 0 && (_jsx("p", { className: 'text-center text-xs text-muted-foreground tabular-nums', children: t('common.showingCount', {
|
|
1345
|
-
defaultValue: '{{count}} de {{total}}',
|
|
1346
|
-
count: data.length,
|
|
1347
|
-
total: rowCount,
|
|
1348
|
-
}) }))) : (_jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions })) })] }), rowActionDialogs, _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), bulkReasonPrompt.dialog, exportEnabled && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), importEnabled && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }));
|
|
1345
|
+
}))) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), forbidden ? (_jsxs(_Fragment, { children: [_jsx("h3", { className: 'text-base font-semibold text-foreground', role: 'alert', children: t('dynamic.forbidden_title', { defaultValue: 'Sin permiso para ver este módulo' }) }), _jsx("p", { className: 'text-sm text-muted-foreground', children: t('dynamic.forbidden_hint', { defaultValue: 'Tu rol no tiene acceso de lectura a estos datos. Pide acceso a un administrador.' }) })] })) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] }))] })), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx(Skeleton, { className: 'h-16 w-full shrink-0', "data-testid": 'table-loading-more-mobile' })), _jsx("div", { ref: infMobileSentinel, className: 'h-1 w-full shrink-0', "aria-hidden": true })] }))] }), _jsx("div", { className: 'shrink-0 pt-4', children: infiniteScroll ? (data.length > 0 && (_jsx("p", { className: 'text-center text-xs text-muted-foreground tabular-nums', children: t('common.showingCount', {
|
|
1346
|
+
defaultValue: '{{count}} de {{total}}',
|
|
1347
|
+
count: data.length,
|
|
1348
|
+
total: rowCount,
|
|
1349
|
+
}) }))) : (_jsx(DataTablePagination, { table: table, pageSizeOptions: metadata.perPageOptions })) })] }), rowActionDialogs, _jsx(AlertDialog, { open: showBulkDeleteConfirm, onOpenChange: (open) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: isBulkDeleting ? 'Eliminando registros...' : '¿Eliminar múltiples registros?' }), _jsx(AlertDialogDescription, { children: isBulkDeleting ? (_jsxs("div", { className: "space-y-4 mt-4", children: [_jsx(Progress, { value: (bulkDeleteProgress / bulkDeleteTotal) * 100 }), _jsxs("p", { className: "text-center text-sm", children: ["Procesando ", bulkDeleteProgress, " de ", bulkDeleteTotal, " registros..."] })] })) : (_jsxs(_Fragment, { children: ["Esta acci\u00F3n no se puede deshacer. Se eliminar\u00E1n permanentemente ", _jsx("strong", { children: Object.keys(rowSelection).length }), " registro(s) de nuestros servidores."] })) })] }), !isBulkDeleting && (_jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); confirmBulkDelete(); }, className: "bg-red-600 hover:bg-red-700", children: "Eliminar todos" })] }))] }) }), bulkReasonPrompt.dialog, exportEnabled && (_jsx(ExportDialog, { open: exportOpen, onOpenChange: setExportOpen, model: model, metadata: metadata, currentFilters: buildFilterParams(), hasActiveFilters: hasActiveFilters })), importEnabled && (_jsx(ImportDialog, { open: importOpen, onOpenChange: setImportOpen, model: model, metadata: metadata, onImported: handleRefresh })), _jsx(DataTableBulkActions, { table: table, entityName: "registro", children: _jsxs(Button, { variant: "destructive", size: "sm", className: "h-8", onClick: () => setShowBulkDeleteConfirm(true), children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5" }), " Eliminar"] }) })] }) }));
|
|
1349
1350
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,12 +27,13 @@ export * from './barcode-scanner';
|
|
|
27
27
|
export * from './form-layout';
|
|
28
28
|
export * from './form-layout-ui';
|
|
29
29
|
export { FieldGrid, FieldCell, FieldLabel } from './field-grid';
|
|
30
|
-
export { ActionModalDispatcher, FEDERATED_ACTION_MODAL_TIMEOUT_MS, type ActionModalProps, } from './action-modal-dispatcher';
|
|
30
|
+
export { ActionModalDispatcher, FEDERATED_ACTION_MODAL_TIMEOUT_MS, resolveFieldDefault, type ActionModalProps, type FieldDefaultContext, } from './action-modal-dispatcher';
|
|
31
|
+
export { todayInZone, isTodayToken } from './calendar-date';
|
|
31
32
|
export { setFederatedActionLoader, type FederatedActionLoader } from './federated-action-loader';
|
|
32
33
|
export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
|
|
33
34
|
export { resolveListPrimaryAction, actionReplacesCreate, type ListPrimaryActionInput, type ListPrimaryActionResult, type ListActionRef, type CreateMode, } from './list-primary-action';
|
|
34
35
|
export { emitRecordMutation, subscribeRecordMutations, RECORD_MUTATION_EVENT, type RecordMutationKind, type RecordMutationDetail, } from './record-mutation-events';
|
|
35
|
-
export { DocumentFormDialog, resolveDocumentForms, scopeDocumentFormsToFilter, type DocumentFormDialogProps, } from './document-form-dialog';
|
|
36
|
+
export { DocumentFormDialog, resolveDocumentForms, scopeDocumentFormsToFilter, delegatedCreate, withoutDelegatedTypes, type DelegatedCreate, type DocumentFormDialogProps, } from './document-form-dialog';
|
|
36
37
|
export { modelFromNavUrl, capabilityForNavItem, isNavItemAllowed, useNavItemVisible, type NavLeafLike, } from './nav-permissions';
|
|
37
38
|
export { CfdiStampPanel, CfdiStampResultDialog, extractStampResult, type CfdiStampResult, type CfdiStampPanelProps, type CfdiStampResultDialogProps, } from './cfdi-stamp-panel';
|
|
38
39
|
export { useRecordMutationTick } from './use-record-mutation-tick';
|
|
@@ -119,4 +120,6 @@ export { getOptionFilter, applyOptionFilter, optionPassesRule } from './option-f
|
|
|
119
120
|
export type { OptionFilter, OptionFilterRule } from './option-filter';
|
|
120
121
|
export { InstalledAddonsProvider, useAddonInstalled, useCapabilityProvided, useInstalledAddons, type InstalledAddonsValue, } from './installed-addons-context';
|
|
121
122
|
export { RecordPrefillBar, type RecordPrefillBarProps } from './record-prefill-bar';
|
|
123
|
+
export { RowActionsMenu, RowActionMenuItem, RowActionsModelContext, type RowActionsMenuProps, } from './row-actions-menu';
|
|
124
|
+
export { classifyActionPriority, isActionProviderActive, isRecordActionProviderActive, resolveRecordActions, splitActionsByPriority, useRecordActions, type ActionPriority, type PrioritizableAction, type RecordActionContext, type RecordActionContribution, type ResolvedRecordAction, type SplitActions, } from './record-actions';
|
|
122
125
|
//# sourceMappingURL=index.d.ts.map
|