@asteby/metacore-runtime-react 34.0.2 → 35.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +5 -84
  2. package/dist/action-modal-dispatcher.d.ts +4 -0
  3. package/dist/action-modal-dispatcher.d.ts.map +1 -1
  4. package/dist/action-modal-dispatcher.js +102 -23
  5. package/dist/addon-loader.d.ts +1 -1
  6. package/dist/addon-loader.d.ts.map +1 -1
  7. package/dist/addon-loader.js +8 -1
  8. package/dist/dialogs/dynamic-record.d.ts.map +1 -1
  9. package/dist/dialogs/dynamic-record.js +107 -52
  10. package/dist/display-value.d.ts +23 -0
  11. package/dist/display-value.d.ts.map +1 -1
  12. package/dist/display-value.js +34 -1
  13. package/dist/dynamic-columns.d.ts +3 -0
  14. package/dist/dynamic-columns.d.ts.map +1 -1
  15. package/dist/dynamic-columns.js +51 -6
  16. package/dist/dynamic-select-field.d.ts.map +1 -1
  17. package/dist/dynamic-select-field.js +8 -9
  18. package/dist/dynamic-table.d.ts.map +1 -1
  19. package/dist/dynamic-table.js +10 -7
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -0
  23. package/dist/metadata-cache.d.ts.map +1 -1
  24. package/dist/metadata-cache.js +8 -1
  25. package/dist/types.d.ts +1 -1
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/use-debounced-value.d.ts +8 -0
  28. package/dist/use-debounced-value.d.ts.map +1 -0
  29. package/dist/use-debounced-value.js +15 -0
  30. package/dist/use-dynamic-filters.d.ts.map +1 -1
  31. package/dist/use-dynamic-filters.js +6 -4
  32. package/dist/use-print-document.d.ts +10 -1
  33. package/dist/use-print-document.d.ts.map +1 -1
  34. package/dist/use-print-document.js +28 -1
  35. package/package.json +1 -1
  36. package/src/__tests__/filename-from-disposition.test.ts +30 -0
  37. package/src/__tests__/image-stack.test.tsx +52 -0
  38. package/src/__tests__/prefill-scalar-from-record.test.ts +37 -0
  39. package/src/__tests__/record-detail-display.test.tsx +30 -0
  40. package/src/__tests__/use-debounced-value.test.ts +56 -0
  41. package/src/action-modal-dispatcher.tsx +110 -22
  42. package/src/addon-loader.tsx +7 -1
  43. package/src/dialogs/dynamic-record.tsx +167 -51
  44. package/src/display-value.tsx +82 -4
  45. package/src/dynamic-columns.tsx +94 -5
  46. package/src/dynamic-select-field.tsx +9 -9
  47. package/src/dynamic-table.tsx +9 -6
  48. package/src/index.ts +7 -0
  49. package/src/metadata-cache.ts +8 -1
  50. package/src/types.ts +2 -0
  51. package/src/use-debounced-value.ts +17 -0
  52. package/src/use-dynamic-filters.ts +6 -4
  53. package/src/use-print-document.ts +38 -2
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  // flows through <ApiProvider> from runtime-react. Host-specific runtime values —
11
11
  // the image-url resolver and the org IANA timezone — are passed as props so the
12
12
  // SDK stays transport- and host-agnostic.
13
- import { createContext, useCallback, useContext, useEffect, useId, useRef, useState } from 'react';
13
+ import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
14
14
  import { useTranslation } from 'react-i18next';
15
15
  /** Model key of the open create/edit dialog — used to hide "+" on self-FK pickers
16
16
  * (e.g. Customer.parent_id) so they don't nest another "Crear Cliente" modal. */
@@ -23,9 +23,7 @@ import { es } from 'date-fns/locale';
23
23
  import { ExternalLink, Loader2, CalendarIcon, ChevronDown, Check, Upload, X as XIcon, ScanLine } from 'lucide-react';
24
24
  import { BarcodeScanner } from '../barcode-scanner';
25
25
  import { useApi } from '../api-context';
26
- import { toastServerError, extractFieldErrors, localizeFieldErrorMap } from '../server-error';
27
- import { validateValues, bagHasErrors } from '../validator';
28
- import { validationCatalog } from '../validation-catalog';
26
+ import { toastServerError, extractFieldErrors, localizeFieldIssue } from '../server-error';
29
27
  import { DynamicSelectField, OptionLead, OptionThumb } from '../dynamic-select-field';
30
28
  import { DynamicRelations } from '../dynamic-relations';
31
29
  import { useOptionsResolver } from '../use-options-resolver';
@@ -39,7 +37,7 @@ import { DynamicIcon, isLucideIconName } from '../dynamic-icon';
39
37
  import { IconPickerField } from '../icon-picker-field';
40
38
  import { humanizeToken } from '../dynamic-columns-helpers';
41
39
  import { formatDateCell } from '../dynamic-columns';
42
- import { OptionBadge, statusColorFor, useIsDarkTheme, } from '../display-value';
40
+ import { ImageStack, OptionBadge, statusColorFor, useIsDarkTheme, } from '../display-value';
43
41
  import { MediaValue, RichText } from '../rich-url';
44
42
  import { generateBadgeStyles } from '@asteby/metacore-ui/lib';
45
43
  import { CollectionCell } from '../collection-cell';
@@ -169,6 +167,23 @@ function isRelationField(field) {
169
167
  !!getFieldRef(field) ||
170
168
  !!field.searchEndpoint);
171
169
  }
170
+ // looksLikeForeignKey — true only for real FKs. A bare `*_id` suffix is NOT
171
+ // enough: columns like `external_id`, `trace_id`, or `invoice_uid` are plain
172
+ // text identifiers from a PAC/provider, not belongs_to relations. Treating them
173
+ // as relations rendered an InitialsAvatar ("6" chip next to "6a8c…") and made
174
+ // fiscal detail modals look broken.
175
+ function looksLikeForeignKey(field) {
176
+ if (isRelationField(field))
177
+ return true;
178
+ if (typeof field.key !== 'string' || !field.key.endsWith('_id'))
179
+ return false;
180
+ const t = String(field.type || '').toLowerCase();
181
+ return (t === 'uuid' ||
182
+ t === 'search' ||
183
+ t === 'relation' ||
184
+ t === 'dynamic_select' ||
185
+ t === 'belongs_to');
186
+ }
172
187
  function formatDisplayValue(rawValue, field) {
173
188
  // Unset nullable FK serialized as the nil UUID renders as empty, not zeros.
174
189
  const value = normalizeNilUuid(rawValue);
@@ -284,19 +299,9 @@ export function stripHiddenFieldValues(values, fields, mode) {
284
299
  }
285
300
  return out;
286
301
  }
287
- function toastValidationFailed(t, lang, localized) {
288
- toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }), {
289
- description: Object.values(localized).filter(Boolean).join('\n'),
290
- });
291
- }
292
302
  export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, lockedFields, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl = identityImageUrl, timeZone, currency, onChange, }) {
293
303
  const api = useApi();
294
- const { t, i18n } = useTranslation();
295
- // Unique per dialog instance. The footer submit lives OUTSIDE <form>, so
296
- // it binds via `form={id}`. A hardcoded id made nested create (product +
297
- // "Crear categoría") submit the PARENT form — toast "Revisa los campos
298
- // marcados" with no marks on the inner modal.
299
- const formId = useId();
304
+ const { t } = useTranslation();
300
305
  const [modalMeta, setModalMeta] = useState(schema ? schema : null);
301
306
  const [relations, setRelations] = useState([]);
302
307
  const [record, setRecord] = useState(null);
@@ -490,25 +495,24 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
490
495
  const labelForKey = (key) => {
491
496
  const f = (modalMeta?.fields ?? []).find(x => x.key === key);
492
497
  if (f?.label)
493
- return t(f.label, { defaultValue: f.label });
498
+ return f.label;
494
499
  return key.replace(/[._-]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
495
500
  };
496
- const lang = i18n.language;
497
501
  // Turn a failed submit (422 `errors` map, or a bare `{errors}` body) into
498
502
  // inline field errors + a summary toast. When there is no field map, fall
499
503
  // back to the existing single cause-carrying toast.
500
504
  const handleSubmitError = (err) => {
501
505
  const map = extractFieldErrors(err);
502
506
  if (map) {
503
- const labels = {};
504
- for (const f of modalMeta?.fields ?? [])
505
- labels[f.key] = labelForKey(f.key);
506
- const localized = localizeFieldErrorMap(map, t, { labels, language: lang });
507
- setFieldErrors(localized);
508
- toastValidationFailed(t, lang, localized);
507
+ const next = {};
508
+ for (const [key, issues] of Object.entries(map)) {
509
+ next[key] = localizeFieldIssue(issues[0], labelForKey(key), t);
510
+ }
511
+ setFieldErrors(next);
512
+ toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }));
509
513
  return;
510
514
  }
511
- toastServerError(err, { t, language: lang, fallback: t('dynamic.save_error', { defaultValue: 'No se pudo guardar' }) });
515
+ toastServerError(err, { t, fallback: t('dynamic.save_error', { defaultValue: 'No se pudo guardar' }) });
512
516
  };
513
517
  const handleSubmit = async (e) => {
514
518
  e?.preventDefault();
@@ -520,15 +524,15 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
520
524
  // fields are gated: a field hidden by its `visible_when` predicate
521
525
  // must not block submit even when it is declared required (matching
522
526
  // the render, which drops it via the same filter).
523
- const visible = filterVisibleFields(modalMeta.fields, mode, formValues);
524
- const bag = validateValues(visible, formValues);
525
- if (bagHasErrors(bag)) {
526
- const labels = {};
527
- for (const f of visible)
528
- labels[f.key] = t(f.label, { defaultValue: f.label });
529
- const localized = localizeFieldErrorMap(bag, t, { labels, language: lang });
530
- setFieldErrors(localized);
531
- toastValidationFailed(t, lang, localized);
527
+ const missing = {};
528
+ for (const field of filterVisibleFields(modalMeta.fields, mode, formValues)) {
529
+ if (field.required && !formValues[field.key] && formValues[field.key] !== 0 && formValues[field.key] !== false) {
530
+ missing[field.key] = localizeFieldIssue({ code: 'required' }, field.label, t);
531
+ }
532
+ }
533
+ if (Object.keys(missing).length) {
534
+ setFieldErrors(missing);
535
+ toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }));
532
536
  return;
533
537
  }
534
538
  }
@@ -649,21 +653,22 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
649
653
  // then advance. Mirrors handleSubmit's required check but scoped to the step.
650
654
  const goNextStep = () => {
651
655
  const step = groups[clampedStep];
652
- const bag = validateValues((step?.fields ?? []), formValues);
653
- if (bagHasErrors(bag)) {
654
- const labels = {};
655
- for (const f of step?.fields ?? [])
656
- labels[f.key] = t(f.label, { defaultValue: f.label });
657
- const localized = localizeFieldErrorMap(bag, t, { labels, language: lang });
658
- setFieldErrors(localized);
659
- toastValidationFailed(t, lang, localized);
656
+ const missing = {};
657
+ for (const field of step?.fields ?? []) {
658
+ if (field.required && !formValues[field.key] && formValues[field.key] !== 0 && formValues[field.key] !== false) {
659
+ missing[field.key] = localizeFieldIssue({ code: 'required' }, field.label, t);
660
+ }
661
+ }
662
+ if (Object.keys(missing).length) {
663
+ setFieldErrors(missing);
664
+ toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }));
660
665
  return;
661
666
  }
662
667
  setFieldErrors({});
663
668
  setStepIndex(Math.min(clampedStep + 1, groups.length - 1));
664
669
  };
665
670
  const goBackStep = () => setStepIndex(Math.max(clampedStep - 1, 0));
666
- return (_jsx(RecordDialogModelContext.Provider, { value: model, children: _jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90dvh] flex flex-col p-0 gap-0 overflow-hidden", style: { maxHeight: '90dvh' }, children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsx(ImageUrlContext.Provider, { value: getImageUrl, children: _jsx(TimeZoneContext.Provider, { value: timeZone, children: _jsxs(CurrencyContext.Provider, { value: currency, children: [_jsxs("form", { id: formId, onSubmit: handleSubmit, className: "grid gap-y-4", children: [isSteps && (_jsx(WizardProgress, { groups: groups, stepIndex: clampedStep })), (isSteps ? [groups[clampedStep]] : groups).map(group => (_jsx(FieldSection, { group: group, children: _jsx("div", { className: "grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2", children: renderFields(group.fields) }) }, group.key))), record?.external_url && (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2", children: _jsx("div", { className: "sm:col-span-2 min-w-0", children: _jsxs("a", { href: record.external_url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Ver en ", record.external_provider ?? 'proveedor externo'] }) }) }))] }), !isCreate && record && relations.length > 0 && (_jsx("div", { className: "mt-6", children: _jsx(DynamicRelations, { record: record, relations: relations, lineSubtable: true, embedOnly: true, canCreate: mode === 'edit', canEdit: mode === 'edit', canDelete: mode === 'edit', onChange: handleChildChange }) }))] }) }) }) })) : null }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0 sm:justify-between", children: [isView && onOpenFullPage ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "text-muted-foreground", onClick: () => { onOpenChange(false); onOpenFullPage(); }, children: [_jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }), "Ver p\u00E1gina completa"] })) : _jsx("span", {}), _jsxs("div", { className: "flex items-center gap-2", children: [isSteps && clampedStep > 0 ? (_jsx(Button, { variant: "outline", onClick: goBackStep, disabled: saving || deleting, children: "Anterior" })) : (_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || deleting, children: config.cancelLabel })), isView && onDelete && (_jsxs(Button, { variant: "destructive", onClick: handleDelete, disabled: deleting || loading, children: [deleting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), deleting ? 'Eliminando...' : 'Eliminar'] })), isView && onEdit && (_jsx(Button, { onClick: onEdit, disabled: deleting || loading, children: "Editar" })), isEditable && isSteps && !isLastStep && (_jsx(Button, { type: "button", onClick: goNextStep, disabled: saving || loading, children: "Siguiente" })), isEditable && (!isSteps || isLastStep) && (_jsxs(Button, { type: "submit", form: formId, disabled: saving || loading, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? config.submittingLabel : config.submitLabel] }))] })] })] }) }) }));
671
+ return (_jsx(RecordDialogModelContext.Provider, { value: model, children: _jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl max-h-[90dvh] flex flex-col p-0 gap-0 overflow-hidden", style: { maxHeight: '90dvh' }, children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: config.description })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: loading ? (_jsx(LoadingSkeleton, {})) : modalMeta ? (_jsx(ModelContext.Provider, { value: model, children: _jsx(ImageUrlContext.Provider, { value: getImageUrl, children: _jsx(TimeZoneContext.Provider, { value: timeZone, children: _jsxs(CurrencyContext.Provider, { value: currency, children: [_jsxs("form", { id: "dynamic-record-form", onSubmit: handleSubmit, className: "grid gap-y-4", children: [isSteps && (_jsx(WizardProgress, { groups: groups, stepIndex: clampedStep })), (isSteps ? [groups[clampedStep]] : groups).map(group => (_jsx(FieldSection, { group: group, children: _jsx("div", { className: "grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2", children: renderFields(group.fields) }) }, group.key))), record?.external_url && (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2", children: _jsx("div", { className: "sm:col-span-2 min-w-0", children: _jsxs("a", { href: record.external_url, target: "_blank", rel: "noreferrer", className: "inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Ver en ", record.external_provider ?? 'proveedor externo'] }) }) }))] }), !isCreate && record && relations.length > 0 && (_jsx("div", { className: "mt-6", children: _jsx(DynamicRelations, { record: record, relations: relations, lineSubtable: true, embedOnly: true, canCreate: mode === 'edit', canEdit: mode === 'edit', canDelete: mode === 'edit', onChange: handleChildChange }) }))] }) }) }) })) : null }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0 sm:justify-between", children: [isView && onOpenFullPage ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "text-muted-foreground", onClick: () => { onOpenChange(false); onOpenFullPage(); }, children: [_jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }), "Ver p\u00E1gina completa"] })) : _jsx("span", {}), _jsxs("div", { className: "flex items-center gap-2", children: [isSteps && clampedStep > 0 ? (_jsx(Button, { variant: "outline", onClick: goBackStep, disabled: saving || deleting, children: "Anterior" })) : (_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: saving || deleting, children: config.cancelLabel })), isView && onDelete && (_jsxs(Button, { variant: "destructive", onClick: handleDelete, disabled: deleting || loading, children: [deleting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), deleting ? 'Eliminando...' : 'Eliminar'] })), isView && onEdit && (_jsx(Button, { onClick: onEdit, disabled: deleting || loading, children: "Editar" })), isEditable && isSteps && !isLastStep && (_jsx(Button, { type: "button", onClick: goNextStep, disabled: saving || loading, children: "Siguiente" })), isEditable && (!isSteps || isLastStep) && (_jsxs(Button, { type: "submit", form: "dynamic-record-form", disabled: saving || loading, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? config.submittingLabel : config.submitLabel] }))] })] })] }) }) }));
667
672
  }
668
673
  function LoadingSkeleton() {
669
674
  return (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3.5 w-24" }), _jsx(Skeleton, { className: "h-9 w-full" })] }, i))) }));
@@ -698,9 +703,7 @@ export function ReadonlyEditField({ field, value }) {
698
703
  // ReadonlyRelationField — a locked/readonly FK field (customer_id, category_id…)
699
704
  // resolves the record's label instead of showing the raw id, mirroring
700
705
  // RelationViewValue's lookup but rendered as a disabled input to match the rest
701
- // of ReadonlyEditField. Without this, a locked relation field (e.g. `lockedFields`
702
- // seeding a POS-selected customer into a vehicle create modal) would show a bare
703
- // UUID — the exact readability bug this dialog otherwise avoids elsewhere.
706
+ // of ReadonlyEditField.
704
707
  function ReadonlyRelationField({ field, value, fieldRef, }) {
705
708
  const rawVal = value && typeof value === 'object' ? (value.value ?? value.id) : value;
706
709
  const needResolve = fieldRef != null || !!field.searchEndpoint;
@@ -720,7 +723,9 @@ function ReadonlyRelationField({ field, value, fieldRef, }) {
720
723
  // RelationViewValue — read-only FK lead. Resolves the relation's label + image
721
724
  // from (1) the sibling object the table served, then (2) the canonical options
722
725
  // endpoint, and renders an OptionLead (thumbnail / icon / color dot) + label.
723
- function RelationViewValue({ field, value, record }) {
726
+ // When `stack` is true (`display: "image_stack"`), the landscape mark sits ON
727
+ // TOP of the label — wide logos (brand marks) fit without cropping.
728
+ function RelationViewValue({ field, value, record, stack = false, }) {
724
729
  const getImageUrl = useContext(ImageUrlContext);
725
730
  const sib = relationSiblingValue(field, record);
726
731
  const sibLabel = typeof sib === 'string' ? sib : objectLabel(sib);
@@ -750,6 +755,9 @@ function RelationViewValue({ field, value, record }) {
750
755
  if (!label && !image) {
751
756
  return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
752
757
  }
758
+ if (stack) {
759
+ return (_jsx("div", { className: "py-1", children: _jsx(ImageStack, { src: image || undefined, label: label, getImageUrl: getImageUrl, size: "lg" }) }));
760
+ }
753
761
  const lead = {
754
762
  image: image ? getImageUrl(image) : null,
755
763
  color: resolved?.color ?? null,
@@ -801,10 +809,15 @@ export function ViewValue({ field, value: rawValue, record, getImageUrl: getImag
801
809
  return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
802
810
  }
803
811
  const value = normalizeNilUuid(rawValue);
804
- // Relation (search / dynamic_select / ref / any *_id) resolved thumbnail +
805
- // label. The *_id catch-all covers plain-typed FK columns not tagged as a
806
- // relation field.
807
- if (isRelationField(field) || (typeof field.key === 'string' && field.key.endsWith('_id'))) {
812
+ // Landscape stack on a relation FK (brand marks, product cards): image ON
813
+ // TOP, label UNDERNEATH. Checked before the default relation lead so a
814
+ // `display: "image_stack"` FK does not fall through to the side-by-side chip.
815
+ if (renderAs === 'image_stack' && looksLikeForeignKey(field)) {
816
+ return _jsx(RelationViewValue, { field: field, value: value, record: record, stack: true });
817
+ }
818
+ // Relation (search / dynamic_select / ref / uuid *_id FK) → resolved
819
+ // thumbnail + label. Plain text `*_id` columns (external_id, …) stay text.
820
+ if (looksLikeForeignKey(field)) {
808
821
  return _jsx(RelationViewValue, { field: field, value: value, record: record });
809
822
  }
810
823
  // The value is itself a resolved object the backend served inline — render
@@ -819,7 +832,24 @@ export function ViewValue({ field, value: rawValue, record, getImageUrl: getImag
819
832
  if (field.type === 'color') {
820
833
  return value ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-5 w-5 rounded-full border shadow-sm", style: { backgroundColor: value } }), _jsx("span", { className: "text-sm", children: value })] })) : (_jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "-" }));
821
834
  }
822
- if (field.type === 'image') {
835
+ // Landscape stack for image/logo URL columns (and `type: image` with
836
+ // `cellStyle: image_stack`). Wide marks sit above an optional caption.
837
+ if (renderAs === 'image_stack') {
838
+ if (value && isLucideIconName(value)) {
839
+ return _jsx(IconNameViewValue, { name: value });
840
+ }
841
+ const labelField = (field.styleConfig &&
842
+ field.styleConfig.label_field) ||
843
+ (field.styleConfig && field.styleConfig.labelField);
844
+ let caption;
845
+ if (labelField && record && typeof record === 'object') {
846
+ const raw = record[labelField];
847
+ if (raw != null && String(raw) !== '')
848
+ caption = String(raw);
849
+ }
850
+ return value || caption ? (_jsx("div", { className: "py-1", children: _jsx(ImageStack, { src: value ? String(value) : undefined, label: caption, getImageUrl: getImageUrl, size: "lg" }) })) : (_jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "Sin imagen" }));
851
+ }
852
+ if (field.type === 'image' || renderAs === 'image') {
823
853
  if (isLucideIconName(value)) {
824
854
  return _jsx(IconNameViewValue, { name: value });
825
855
  }
@@ -990,8 +1020,33 @@ function StructuredViewValue({ value, field, locale, t, }) {
990
1020
  if (isEmpty) {
991
1021
  return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
992
1022
  }
1023
+ // Line-items arrays with a declared itemFields schema → mini-table.
1024
+ // Plain objects (PAC provider_data, fiscal_data bags) → readable key/value
1025
+ // list; nested objects/arrays render as pretty JSON instead of `key: {…}`
1026
+ // stubs that looked broken in fiscal detail modals.
1027
+ const hasItemFields = !!(field?.itemFields ?? field?.item_fields);
1028
+ if (!hasItemFields &&
1029
+ value !== null &&
1030
+ typeof value === 'object' &&
1031
+ !Array.isArray(value)) {
1032
+ return _jsx(JsonObjectViewValue, { value: value });
1033
+ }
993
1034
  return (_jsx("div", { className: "text-sm py-1", children: _jsx(CollectionCell, { value: value, itemFields: field?.itemFields ?? field?.item_fields, variant: "inline", locale: locale, t: t, getImageUrl: getImageUrl }) }));
994
1035
  }
1036
+ /** Flatten a jsonb/object bag into labeled rows; nest as pretty JSON. */
1037
+ function JsonObjectViewValue({ value }) {
1038
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined);
1039
+ if (entries.length === 0) {
1040
+ return _jsx("p", { className: "text-sm py-1 text-muted-foreground", children: "\u2014" });
1041
+ }
1042
+ return (_jsx("dl", { className: "grid gap-2 py-1 text-sm", children: entries.map(([key, raw]) => {
1043
+ const label = humanizeToken(key);
1044
+ const isNest = raw !== null &&
1045
+ typeof raw === 'object' &&
1046
+ !(raw instanceof Date);
1047
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: label }), _jsx("dd", { className: "mt-0.5 break-words text-foreground", children: isNest ? (_jsx("pre", { className: "max-h-48 overflow-auto rounded-md bg-muted/40 p-2 text-[11px] leading-relaxed whitespace-pre-wrap", children: JSON.stringify(raw, null, 2) })) : raw === null || raw === '' ? (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) : (String(raw)) })] }, key));
1048
+ }) }));
1049
+ }
995
1050
  export function EditField({ field, value, onChange, record }) {
996
1051
  const { t, i18n } = useTranslation();
997
1052
  const editFieldImageUrl = useContext(ImageUrlContext);
@@ -33,6 +33,29 @@ export declare const RelationThumbnail: React.FC<{
33
33
  getImageUrl?: (path: string) => string;
34
34
  size?: number;
35
35
  }>;
36
+ /**
37
+ * Landscape-friendly identity block: wide image ON TOP, label (and optional
38
+ * subtitle) UNDERNEATH. Use for brand logos, product photos and any mark that
39
+ * is wider than it is tall — a square thumb next to text crops logos and
40
+ * wastes the cell. Declared via `display: "image_stack"` on a column (image
41
+ * URL or FK relation with a logo/photo sibling).
42
+ *
43
+ * No padded card / muted plate behind the mark — that ate table row height.
44
+ * The image is sized with max-width/max-height and `object-contain` only.
45
+ *
46
+ * Sizes (CSS, not Tailwind arbitrary — host safelists vary):
47
+ * sm — compact table (~88×32)
48
+ * md — default table (~112×40)
49
+ * lg — detail/modal (~180×72)
50
+ */
51
+ export declare const ImageStack: React.FC<{
52
+ src?: string | null;
53
+ label?: string | null;
54
+ subtitle?: string | null;
55
+ getImageUrl?: (path: string) => string;
56
+ size?: 'sm' | 'md' | 'lg';
57
+ className?: string;
58
+ }>;
36
59
  export interface DisplayOption {
37
60
  value: string;
38
61
  label: string;
@@ -1 +1 @@
1
- {"version":3,"file":"display-value.d.ts","sourceRoot":"","sources":["../src/display-value.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAepD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAkBA,CAAA;AAED,MAAM,WAAW,aAAa;IAC1B,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;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CA8BA,CAAA"}
1
+ {"version":3,"file":"display-value.d.ts","sourceRoot":"","sources":["../src/display-value.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAepD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAiBA,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,CAuDA,CAAA;AAED,MAAM,WAAW,aAAa;IAC1B,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;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CA8BA,CAAA"}
@@ -58,7 +58,40 @@ export function statusColorFor(value) {
58
58
  * an addon-arbitrary Tailwind class never gets dropped by a consuming app's
59
59
  * class scan). Rendered inline alongside a label — never alone.
60
60
  */
61
- export const RelationThumbnail = ({ src, alt, getImageUrl, size = 20 }) => (_jsxs(Avatar, { className: "shrink-0 rounded-md bg-muted/50 p-[3px] ring-1 ring-border/40", style: { width: size, height: size }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(src) : src, alt: alt, className: "object-contain" }), _jsx(AvatarFallback, { className: "rounded-md bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(alt) })] }));
61
+ export const RelationThumbnail = ({ src, alt, getImageUrl, size = 20 }) => (_jsxs(Avatar, { className: "shrink-0 rounded-md", style: { width: size, height: size }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(src) : src, alt: alt, className: "object-contain" }), _jsx(AvatarFallback, { className: "rounded-md bg-transparent text-[8px] font-bold text-muted-foreground", children: getInitials(alt) })] }));
62
+ /**
63
+ * Landscape-friendly identity block: wide image ON TOP, label (and optional
64
+ * subtitle) UNDERNEATH. Use for brand logos, product photos and any mark that
65
+ * is wider than it is tall — a square thumb next to text crops logos and
66
+ * wastes the cell. Declared via `display: "image_stack"` on a column (image
67
+ * URL or FK relation with a logo/photo sibling).
68
+ *
69
+ * No padded card / muted plate behind the mark — that ate table row height.
70
+ * The image is sized with max-width/max-height and `object-contain` only.
71
+ *
72
+ * Sizes (CSS, not Tailwind arbitrary — host safelists vary):
73
+ * sm — compact table (~88×32)
74
+ * md — default table (~112×40)
75
+ * lg — detail/modal (~180×72)
76
+ */
77
+ export const ImageStack = ({ src, label, subtitle, getImageUrl, size = 'md', className }) => {
78
+ const dims = size === 'lg'
79
+ ? { w: 180, h: 72, text: 'text-sm' }
80
+ : size === 'sm'
81
+ ? { w: 88, h: 32, text: 'text-[11px]' }
82
+ : { w: 112, h: 40, text: 'text-xs' };
83
+ const alt = label || '';
84
+ const resolved = src
85
+ ? getImageUrl
86
+ ? getImageUrl(src)
87
+ : src
88
+ : undefined;
89
+ const [broken, setBroken] = React.useState(false);
90
+ React.useEffect(() => {
91
+ setBroken(false);
92
+ }, [resolved]);
93
+ return (_jsxs("span", { className: `inline-flex max-w-[220px] flex-col items-start gap-0.5 ${className ?? ''}`, title: subtitle ? `${alt} · ${subtitle}` : alt || undefined, children: [resolved && !broken ? (_jsx("img", { src: resolved, alt: alt, className: "block object-contain", style: { maxWidth: dims.w, maxHeight: dims.h, width: 'auto', height: 'auto' }, onError: () => setBroken(true) })) : (_jsx("span", { className: "flex items-center text-[10px] font-semibold tracking-wide text-muted-foreground", style: { minHeight: Math.round(dims.h * 0.55) }, children: getInitials(alt || '?') })), (label || subtitle) && (_jsxs("span", { className: `flex w-full min-w-0 flex-col items-start leading-tight ${dims.text}`, children: [label ? (_jsx("span", { className: "w-full truncate text-left font-medium text-foreground/90", children: label })) : null, subtitle ? (_jsx("span", { className: "w-full truncate text-left text-[0.7em] text-muted-foreground", children: subtitle })) : null] }))] }));
94
+ };
62
95
  /**
63
96
  * Colored option badge — the canonical "pro" pill for a select/status/badge
64
97
  * value. Explicit backend `color` wins; otherwise a stable, cohesive color is
@@ -173,6 +173,9 @@ export declare const resolveRelationSubtitle: (col: ColumnDefinition, row: any)
173
173
  export declare const ImageCell: React.FC<{
174
174
  value: unknown;
175
175
  getImageUrl: (path: string) => string;
176
+ /** Optional caption under the image (display: image_stack). */
177
+ label?: string;
178
+ stack?: boolean;
176
179
  }>;
177
180
  export declare function makeDefaultGetDynamicColumns(helpers?: DynamicColumnsHelpers): GetDynamicColumns;
178
181
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"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"}
@@ -23,7 +23,7 @@ import { generateBadgeStyles, getInitials, relationChipStyles, } from '@asteby/m
23
23
  import { Progress } from './dialogs/_primitives';
24
24
  import { humanizeToken } from './dynamic-columns-helpers';
25
25
  import { objectLabel } from './dynamic-relation-helpers';
26
- import { OptionBadge, RelationThumbnail, statusColorFor, useIsDarkTheme, } from './display-value';
26
+ import { OptionBadge, RelationThumbnail, ImageStack, statusColorFor, useIsDarkTheme, } from './display-value';
27
27
  import { MediaValue } from './rich-url';
28
28
  import { OptionsContext } from './options-context';
29
29
  import { DynamicIcon, isLucideIconName } from './dynamic-icon';
@@ -369,7 +369,7 @@ export const resolveRelationLabel = (col, row) => {
369
369
  export const resolveRelationImage = (col, row) => {
370
370
  const sibling = getNestedValue(row, relationKeyFor(col));
371
371
  if (sibling && typeof sibling === 'object') {
372
- const img = sibling.image ?? sibling.avatar ?? sibling.photo;
372
+ const img = sibling.image ?? sibling.avatar ?? sibling.photo ?? sibling.logo ?? sibling.thumbnail;
373
373
  if (img !== undefined && img !== null && img !== '')
374
374
  return String(img);
375
375
  }
@@ -468,13 +468,20 @@ export const resolveRelationSubtitle = (col, row) => {
468
468
  * carries an `image`. Falls back to the raw id when no sibling was resolved, and
469
469
  * to an empty marker when there is no value at all. Domain-agnostic: works for
470
470
  * every `belongs_to` column (category, supplier, brand, …) without per-addon code.
471
+ *
472
+ * When `stack` is true (column `display: "image_stack"`), the landscape mark
473
+ * sits ON TOP of the label — wide logos fit without cropping and the cell
474
+ * stays readable in dense tables.
471
475
  */
472
- const RelationCell = ({ col, row, getImageUrl }) => {
476
+ const RelationCell = ({ col, row, getImageUrl, stack = false }) => {
473
477
  const display = resolveRelationLabel(col, row);
474
478
  if (!display)
475
479
  return _jsx(EmptyCell, {});
476
480
  const image = resolveRelationImage(col, row);
477
481
  const subtitle = resolveRelationSubtitle(col, row);
482
+ if (stack) {
483
+ return (_jsx(ImageStack, { src: image || undefined, label: display, subtitle: subtitle || undefined, getImageUrl: getImageUrl, size: "md" }));
484
+ }
478
485
  // FLAT reference cell: no tinted capsule around the pair. A reference is
479
486
  // data, not a status — the pill treatment (and its per-label tint) made a
480
487
  // products/warehouses listing read as a wall of badges. What identifies the
@@ -537,12 +544,17 @@ const AvatarCell = ({ name, desc, avatarSrc, getImageUrl }) => (_jsxs("div", { c
537
544
  * form widget stores — renders the glyph instead of an <img> that would 404
538
545
  * into an empty grey box. Exported for tests.
539
546
  */
540
- export const ImageCell = ({ value, getImageUrl }) => {
541
- if (!value)
547
+ export const ImageCell = ({ value, getImageUrl, label, stack = false }) => {
548
+ if (!value && !label)
542
549
  return _jsx("span", { className: "text-muted-foreground", children: "-" });
543
- if (isLucideIconName(value)) {
550
+ if (value && isLucideIconName(value)) {
544
551
  return (_jsx("div", { className: "h-10 w-10 flex items-center justify-center rounded bg-muted", children: _jsx(DynamicIcon, { name: value, className: "h-5 w-5" }) }));
545
552
  }
553
+ if (stack) {
554
+ return (_jsx(ImageStack, { src: value ? String(value) : undefined, label: label, getImageUrl: getImageUrl, size: "md" }));
555
+ }
556
+ if (!value)
557
+ return _jsx("span", { className: "text-muted-foreground", children: "-" });
546
558
  return (_jsx("div", { className: "h-10 w-10 relative rounded overflow-hidden bg-muted flex items-center justify-center", children: _jsx("img", { src: getImageUrl(String(value)), alt: "Thumbnail", className: "h-full w-full object-contain", onError: (e) => {
547
559
  ;
548
560
  e.currentTarget.style.display = 'none';
@@ -649,6 +661,29 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
649
661
  if (renderAs === 'reference') {
650
662
  return _jsx(ReferenceCell, { col: col, row: row.original });
651
663
  }
664
+ // Landscape stack: wide image ON TOP, label UNDERNEATH.
665
+ // Declared via `display: "image_stack"` on an image column
666
+ // or on a belongs_to FK whose sibling carries a logo/photo
667
+ // (brand marks, product cards). Fits logos that are wider
668
+ // than tall without cropping into a square thumb.
669
+ if (renderAs === 'image_stack') {
670
+ // FK relation (brand_id → brands) OR any column that
671
+ // already resolved a sibling with an image — stack it.
672
+ // Don't require `col.ref` alone: enrichment sometimes
673
+ // leaves type=text while cellStyle carries image_stack.
674
+ const looksRelation = !!col.ref ||
675
+ (typeof col.key === 'string' &&
676
+ col.key.endsWith('_id') &&
677
+ resolveRelationLabel(col, row.original) != null);
678
+ if (looksRelation) {
679
+ return (_jsx(RelationCell, { col: col, row: row.original, getImageUrl: getImageUrl, stack: true }));
680
+ }
681
+ const labelField = styleCfg(col, 'label_field', 'labelField');
682
+ const caption = labelField
683
+ ? String(getNestedValue(row.original, labelField) ?? '')
684
+ : undefined;
685
+ return (_jsx(ImageCell, { value: value, getImageUrl: getImageUrl, label: caption || undefined, stack: true }));
686
+ }
652
687
  // Resolved FK relation chip. Triggers on an explicit
653
688
  // `cellStyle: 'relation'` or on any column carrying a `ref`
654
689
  // (a belongs_to FK) that isn't being rendered as an
@@ -856,6 +891,16 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
856
891
  : null);
857
892
  return _jsx(ImageCell, { value: imageValue, getImageUrl: getImageUrl });
858
893
  }
894
+ case 'image_stack': {
895
+ // Defensive: normally handled above before the
896
+ // switch; kept so a late `type: image_stack` without
897
+ // cellStyle still stacks.
898
+ const labelField = styleCfg(col, 'label_field', 'labelField');
899
+ const caption = labelField
900
+ ? String(getNestedValue(row.original, labelField) ?? '')
901
+ : undefined;
902
+ return (_jsx(ImageCell, { value: value, getImageUrl: getImageUrl, label: caption || undefined, stack: true }));
903
+ }
859
904
  default: {
860
905
  if (typeof value === 'object' && value !== null) {
861
906
  return (_jsx(CollectionCell, { value: value, locale: currentLanguage, t: t, itemFields: col.itemFields ?? col.item_fields, getImageUrl: getImageUrl }));
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,IAAI,EACJ,IAAS,GACZ,EAAE;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,+BA4BA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCA4BA;AAWD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;IAChD;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,EAChB,aAAoB,EACpB,kBAA0B,EAC1B,UAAkB,GACrB,EAAE,uBAAuB,+BA8SzB;AAED,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AA4CA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,IAAI,EACJ,IAAS,GACZ,EAAE;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,+BA4BA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCA4BA;AAMD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;IAChD;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,EAChB,aAAoB,EACpB,kBAA0B,EAC1B,UAAkB,GACrB,EAAE,uBAAuB,+BAiTzB;AAED,eAAe,kBAAkB,CAAA"}
@@ -23,11 +23,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
23
  // value). A dedicated `?ids=` lookup is a follow-up; create flows — the common
24
24
  // case — start empty and never hit this.
25
25
  import { useEffect, useRef, useState } from 'react';
26
+ import { useTranslation } from 'react-i18next';
26
27
  import { Badge, Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, InitialsAvatar, } from '@asteby/metacore-ui/primitives';
27
28
  import { Check, ChevronsUpDown, Loader2, Plus, ScanLine } from 'lucide-react';
28
29
  import { resolveColorCss } from '@asteby/metacore-ui/lib';
29
30
  import { BarcodeScanner } from './barcode-scanner';
30
31
  import { DynamicIcon, isLucideIconName } from './dynamic-icon';
32
+ import { useDebouncedValue } from './use-debounced-value';
31
33
  import { useOptionsResolver } from './use-options-resolver';
32
34
  import { getDependsOn, getFieldRef, resolveOptionsSource } from './dynamic-form-schema';
33
35
  /**
@@ -84,14 +86,11 @@ export function OptionLead({ option, size = 20, }) {
84
86
  return null;
85
87
  }
86
88
  function useDebounced(value, ms) {
87
- const [debounced, setDebounced] = useState(value);
88
- useEffect(() => {
89
- const t = setTimeout(() => setDebounced(value), ms);
90
- return () => clearTimeout(t);
91
- }, [value, ms]);
92
- return debounced;
89
+ return useDebouncedValue(value, ms);
93
90
  }
94
91
  export function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly = false, staticOptions = null, descriptionAsBadge = false, hideCreate = false, }) {
92
+ const { t } = useTranslation();
93
+ const ph = (fallback) => field.placeholder ? t(field.placeholder, { defaultValue: field.placeholder }) : fallback;
95
94
  const [open, setOpen] = useState(false);
96
95
  const [search, setSearch] = useState('');
97
96
  const [scanOpen, setScanOpen] = useState(false);
@@ -210,7 +209,7 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
210
209
  // the eager fetch is in flight the label falls back to the seed/raw value,
211
210
  // then snaps to the name once options arrive.
212
211
  if (readonly) {
213
- return (_jsx(Button, { type: "button", variant: "outline", role: "combobox", id: field.key, disabled: true, "aria-readonly": "true", className: "w-full min-w-0 cursor-default justify-start font-normal opacity-100", children: _jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [selectedOption ? _jsx(OptionLead, { option: selectedOption, size: 20 }) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedOption ? '' : 'text-muted-foreground'), children: selectedOption?.label ?? (loading ? 'Cargando…' : field.placeholder || '—') })] }) }));
212
+ return (_jsx(Button, { type: "button", variant: "outline", role: "combobox", id: field.key, disabled: true, "aria-readonly": "true", className: "w-full min-w-0 cursor-default justify-start font-normal opacity-100", children: _jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [selectedOption ? _jsx(OptionLead, { option: selectedOption, size: 20 }) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedOption ? '' : 'text-muted-foreground'), children: selectedOption?.label ?? (loading ? 'Cargando…' : ph('—')) })] }) }));
214
213
  }
215
214
  // w-full + min-w-0: as a grid cell child, the row must be allowed to shrink
216
215
  // to the cell. Without min-w-0 the combobox+button row sizes to its content
@@ -219,10 +218,10 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
219
218
  return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsxs(Popover, { open: open && !blockedByDependency, onOpenChange: (o) => { if (!blockedByDependency)
220
219
  setOpen(o); }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, disabled: blockedByDependency, className: "min-w-0 flex-1 justify-between font-normal", "data-empty": !value, "data-depends-blocked": blockedByDependency ? '' : undefined, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [value && selectedOption ? (_jsx(OptionLead, { option: selectedOption, size: 20 })) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: blockedByDependency
221
220
  ? (dependsHint || DEFAULT_DEPENDS_HINT)
222
- : selectedLabel || field.placeholder || 'Buscar…' }), descriptionAsBadge && selectedOption?.description ? (_jsx(Badge, { variant: "secondary", className: "shrink-0 font-normal tabular-nums", children: selectedOption.description })) : null] }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
221
+ : selectedLabel || ph('Buscar…') }), descriptionAsBadge && selectedOption?.description ? (_jsx(Badge, { variant: "secondary", className: "shrink-0 font-normal tabular-nums", children: selectedOption.description })) : null] }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
223
222
  // Match the trigger width without an arbitrary Tailwind class
224
223
  // (those don't always survive a consuming app's Tailwind scan).
225
- style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: field.placeholder || 'Buscar…', value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: useStatic
224
+ style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: ph('Buscar…'), value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: useStatic
226
225
  ? debounced
227
226
  ? 'Sin resultados'
228
227
  : 'Sin opciones'
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AA2FnF,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,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;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+BAgvCnB"}
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AA4FnF,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,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;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+BAkvCnB"}