@asteby/metacore-runtime-react 46.4.0 → 47.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/action-modal-dispatcher.d.ts +10 -1
  3. package/dist/action-modal-dispatcher.d.ts.map +1 -1
  4. package/dist/action-modal-dispatcher.js +107 -19
  5. package/dist/audit-info.d.ts +54 -0
  6. package/dist/audit-info.d.ts.map +1 -0
  7. package/dist/audit-info.js +142 -0
  8. package/dist/business/catalog.d.ts.map +1 -1
  9. package/dist/business/catalog.js +24 -0
  10. package/dist/business/index.d.ts +3 -0
  11. package/dist/business/index.d.ts.map +1 -1
  12. package/dist/business/index.js +3 -0
  13. package/dist/business/print-send-dialog.d.ts +27 -0
  14. package/dist/business/print-send-dialog.d.ts.map +1 -0
  15. package/dist/business/print-send-dialog.js +55 -0
  16. package/dist/business/relate-documents.d.ts +41 -0
  17. package/dist/business/relate-documents.d.ts.map +1 -0
  18. package/dist/business/relate-documents.js +42 -0
  19. package/dist/business/vehicle-picker.d.ts +36 -0
  20. package/dist/business/vehicle-picker.d.ts.map +1 -0
  21. package/dist/business/vehicle-picker.js +61 -0
  22. package/dist/cfdi-stamp-panel.d.ts +29 -0
  23. package/dist/cfdi-stamp-panel.d.ts.map +1 -0
  24. package/dist/cfdi-stamp-panel.js +53 -0
  25. package/dist/dialogs/dynamic-record.d.ts.map +1 -1
  26. package/dist/dialogs/dynamic-record.js +10 -1
  27. package/dist/document-form-dialog.d.ts +23 -0
  28. package/dist/document-form-dialog.d.ts.map +1 -0
  29. package/dist/document-form-dialog.js +196 -0
  30. package/dist/dynamic-crud-page.d.ts +6 -0
  31. package/dist/dynamic-crud-page.d.ts.map +1 -1
  32. package/dist/dynamic-crud-page.js +4 -2
  33. package/dist/dynamic-form.d.ts.map +1 -1
  34. package/dist/dynamic-form.js +3 -0
  35. package/dist/dynamic-kanban.d.ts.map +1 -1
  36. package/dist/dynamic-kanban.js +11 -2
  37. package/dist/dynamic-line-items.d.ts.map +1 -1
  38. package/dist/dynamic-line-items.js +3 -0
  39. package/dist/dynamic-multi-select-field.d.ts.map +1 -1
  40. package/dist/dynamic-multi-select-field.js +2 -0
  41. package/dist/dynamic-select-field.d.ts.map +1 -1
  42. package/dist/dynamic-select-field.js +4 -0
  43. package/dist/dynamic-table.d.ts.map +1 -1
  44. package/dist/dynamic-table.js +6 -2
  45. package/dist/index.d.ts +8 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +9 -0
  48. package/dist/nav-permissions.d.ts +19 -0
  49. package/dist/nav-permissions.d.ts.map +1 -0
  50. package/dist/nav-permissions.js +51 -0
  51. package/dist/navigation-builder.d.ts +6 -0
  52. package/dist/navigation-builder.d.ts.map +1 -1
  53. package/dist/navigation-builder.js +26 -1
  54. package/dist/option-filter.d.ts +31 -0
  55. package/dist/option-filter.d.ts.map +1 -0
  56. package/dist/option-filter.js +49 -0
  57. package/dist/permissions-manager.d.ts +9 -0
  58. package/dist/permissions-manager.d.ts.map +1 -1
  59. package/dist/permissions-manager.js +50 -17
  60. package/dist/print-document-button.d.ts.map +1 -1
  61. package/dist/print-document-button.js +9 -1
  62. package/dist/record-mutation-events.d.ts +14 -0
  63. package/dist/record-mutation-events.d.ts.map +1 -0
  64. package/dist/record-mutation-events.js +37 -0
  65. package/dist/types.d.ts +86 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/use-options-resolver.d.ts +8 -0
  68. package/dist/use-options-resolver.d.ts.map +1 -1
  69. package/dist/use-options-resolver.js +8 -2
  70. package/dist/use-print-document.d.ts +15 -1
  71. package/dist/use-print-document.d.ts.map +1 -1
  72. package/dist/use-print-document.js +52 -1
  73. package/dist/use-record-mutation-tick.d.ts +6 -0
  74. package/dist/use-record-mutation-tick.d.ts.map +1 -0
  75. package/dist/use-record-mutation-tick.js +11 -0
  76. package/package.json +3 -3
  77. package/src/__tests__/action-modal-feedback.test.tsx +116 -0
  78. package/src/__tests__/action-modal-grid-layout.test.tsx +1 -1
  79. package/src/__tests__/apply-sidebar-layout.test.ts +1 -1
  80. package/src/__tests__/audit-info.test.tsx +103 -0
  81. package/src/__tests__/business-pickers.test.tsx +157 -0
  82. package/src/__tests__/dependent-options.test.tsx +12 -5
  83. package/src/__tests__/document-form-dialog.test.tsx +190 -0
  84. package/src/__tests__/dynamic-table-infinite-scroll.test.tsx +4 -0
  85. package/src/__tests__/dynamic-table-mutation-refresh.test.tsx +116 -0
  86. package/src/__tests__/line-items-lock-rows.test.tsx +2 -1
  87. package/src/__tests__/nav-permissions.test.tsx +76 -0
  88. package/src/__tests__/option-filter.test.tsx +115 -0
  89. package/src/__tests__/permissions-manager.test.tsx +21 -4
  90. package/src/__tests__/structured-view-value.test.tsx +5 -1
  91. package/src/__tests__/use-print-document-feedback.test.tsx +58 -0
  92. package/src/__tests__/wizard-action-modal.test.tsx +1 -1
  93. package/src/action-modal-dispatcher.tsx +124 -6
  94. package/src/audit-info.tsx +219 -0
  95. package/src/business/catalog.ts +24 -0
  96. package/src/business/index.ts +3 -0
  97. package/src/business/print-send-dialog.tsx +175 -0
  98. package/src/business/relate-documents.tsx +182 -0
  99. package/src/business/vehicle-picker.tsx +188 -0
  100. package/src/cfdi-stamp-panel.tsx +146 -0
  101. package/src/dialogs/dynamic-record.tsx +18 -0
  102. package/src/document-form-dialog.tsx +357 -0
  103. package/src/dropdown-menu-entry.test.ts +23 -0
  104. package/src/dynamic-crud-page.tsx +21 -2
  105. package/src/dynamic-form.tsx +3 -0
  106. package/src/dynamic-kanban.tsx +10 -4
  107. package/src/dynamic-line-items.tsx +3 -0
  108. package/src/dynamic-multi-select-field.tsx +2 -0
  109. package/src/dynamic-select-field.tsx +4 -0
  110. package/src/dynamic-table.tsx +6 -2
  111. package/src/index.ts +41 -0
  112. package/src/nav-permissions.ts +58 -0
  113. package/src/navigation-builder.tsx +27 -1
  114. package/src/option-filter.ts +88 -0
  115. package/src/permissions-manager.tsx +39 -26
  116. package/src/print-document-button.tsx +9 -1
  117. package/src/record-mutation-events.ts +50 -0
  118. package/src/types.ts +90 -0
  119. package/src/use-options-resolver.ts +19 -1
  120. package/src/use-print-document.ts +66 -3
  121. package/src/use-record-mutation-tick.ts +12 -0
  122. package/vitest.config.ts +1 -0
  123. package/vitest.setup.ts +7 -0
@@ -16,3 +16,6 @@ export * from './return';
16
16
  export * from './return-wizard';
17
17
  export * from './inspection';
18
18
  export * from './inspection-checklist';
19
+ export * from './vehicle-picker';
20
+ export * from './relate-documents';
21
+ export * from './print-send-dialog';
@@ -0,0 +1,27 @@
1
+ export type SendChannel = 'email' | 'whatsapp';
2
+ export interface PrintableDocument {
3
+ folio: string;
4
+ /** URL del PDF ya generado. Sin ella no hay «Imprimir». */
5
+ pdfUrl?: string;
6
+ /** Texto de cabecera («Factura», «Cotización»). */
7
+ title?: string;
8
+ /** Destinos sugeridos (datos del cliente). */
9
+ email?: string;
10
+ phone?: string;
11
+ }
12
+ export interface PrintSendDialogProps {
13
+ open: boolean;
14
+ onOpenChange: (open: boolean) => void;
15
+ document: PrintableDocument;
16
+ /** Canales de envío disponibles. Default: ambos. Vacío = solo imprimir. */
17
+ channels?: SendChannel[];
18
+ /**
19
+ * Envío a cargo del host. Si rechaza, el mensaje se muestra en el diálogo.
20
+ * Sin `onSend` no se ofrece enviar.
21
+ */
22
+ onSend?: (channel: SendChannel, destination: string, document: PrintableDocument) => Promise<void> | void;
23
+ /** Reemplaza la impresión por defecto (abrir `pdfUrl` en otra pestaña). */
24
+ onPrint?: (document: PrintableDocument) => void;
25
+ }
26
+ export declare function PrintSendDialog({ open, onOpenChange, document: doc, channels, onSend, onPrint, }: PrintSendDialogProps): import("react").JSX.Element;
27
+ //# sourceMappingURL=print-send-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print-send-dialog.d.ts","sourceRoot":"","sources":["../../src/business/print-send-dialog.tsx"],"names":[],"mappings":"AAmBA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,CAAA;AAE9C,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzG,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,CAAA;CAClD;AAOD,wBAAgB,eAAe,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,QAAQ,EAAE,GAAG,EACb,QAAgC,EAChC,MAAM,EACN,OAAO,GACV,EAAE,oBAAoB,+BAmHtB"}
@@ -0,0 +1,55 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ // PrintSendDialog — imprimir o enviar un documento ya emitido (cotización,
3
+ // factura, nota de crédito…). El SDK NO llama a un proveedor de mensajería: el
4
+ // host recibe `onSend(channel, destination, document)` y decide qué hacer.
5
+ // Imprimir usa la URL del PDF que pasa el host (o su propio `onPrint`).
6
+ import { useEffect, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Mail, MessageCircle, Printer, Loader2 } from 'lucide-react';
9
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, } from '@asteby/metacore-ui/primitives';
10
+ import { FormErrorBanner } from './feedback';
11
+ function defaultPrint(doc) {
12
+ if (!doc.pdfUrl || typeof window === 'undefined')
13
+ return;
14
+ window.open(doc.pdfUrl, '_blank', 'noopener,noreferrer');
15
+ }
16
+ export function PrintSendDialog({ open, onOpenChange, document: doc, channels = ['email', 'whatsapp'], onSend, onPrint, }) {
17
+ const { t } = useTranslation();
18
+ const [channel, setChannel] = useState(channels[0] ?? 'email');
19
+ const [destination, setDestination] = useState('');
20
+ const [sending, setSending] = useState(false);
21
+ const [sent, setSent] = useState(false);
22
+ const [error, setError] = useState();
23
+ const suggested = channel === 'email' ? doc.email : doc.phone;
24
+ useEffect(() => {
25
+ if (open) {
26
+ setDestination(suggested ?? '');
27
+ setSent(false);
28
+ setError(undefined);
29
+ }
30
+ }, [open, channel, suggested]);
31
+ const canSend = !!onSend && channels.length > 0;
32
+ const send = async () => {
33
+ const dest = destination.trim();
34
+ if (!onSend || !dest)
35
+ return;
36
+ setSending(true);
37
+ setError(undefined);
38
+ setSent(false);
39
+ try {
40
+ await onSend(channel, dest, doc);
41
+ setSent(true);
42
+ }
43
+ catch (e) {
44
+ const msg = e?.message;
45
+ setError(typeof msg === 'string' && msg
46
+ ? msg
47
+ : t('printSend.error', { defaultValue: 'No se pudo enviar el documento. Inténtalo de nuevo.' }));
48
+ }
49
+ finally {
50
+ setSending(false);
51
+ }
52
+ };
53
+ const channelLabel = (c) => c === 'email' ? t('printSend.email', { defaultValue: 'Correo' }) : t('printSend.whatsapp', { defaultValue: 'WhatsApp' });
54
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: [t('printSend.title', { defaultValue: 'Imprimir o enviar' }), doc.title ? ` · ${doc.title}` : ''] }), _jsx(DialogDescription, { children: doc.folio })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs(Button, { type: "button", variant: "outline", className: "w-full justify-start", disabled: !doc.pdfUrl && !onPrint, onClick: () => (onPrint ?? defaultPrint)(doc), children: [_jsx(Printer, { className: "mr-2 size-4", "aria-hidden": true }), t('printSend.print', { defaultValue: 'Imprimir' })] }), canSend && (_jsxs("div", { className: "space-y-2", children: [channels.length > 1 && (_jsx("div", { className: "flex gap-2", role: "group", "aria-label": t('printSend.channel', { defaultValue: 'Canal de envío' }), children: channels.map((c) => (_jsxs(Button, { type: "button", size: "sm", variant: c === channel ? 'default' : 'outline', "aria-pressed": c === channel, onClick: () => setChannel(c), children: [c === 'email' ? _jsx(Mail, { className: "mr-1.5 size-4", "aria-hidden": true }) : _jsx(MessageCircle, { className: "mr-1.5 size-4", "aria-hidden": true }), channelLabel(c)] }, c))) })), _jsx(Input, { value: destination, type: channel === 'email' ? 'email' : 'tel', "aria-label": t('printSend.destination', { defaultValue: 'Destino' }), placeholder: channel === 'email' ? 'cliente@ejemplo.com' : '+52 55 0000 0000', onChange: (e) => setDestination(e.target.value) }), _jsx(FormErrorBanner, { message: error }), sent && (_jsx("p", { role: "status", className: "text-sm text-emerald-600 dark:text-emerald-400", children: t('printSend.sent', { defaultValue: 'Enviado.' }) }))] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: t('common.close', { defaultValue: 'Cerrar' }) }), canSend && (_jsxs(Button, { type: "button", onClick: send, disabled: sending || !destination.trim(), children: [sending && _jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": true }), t('printSend.send', { defaultValue: 'Enviar' })] }))] })] }) }));
55
+ }
@@ -0,0 +1,41 @@
1
+ export interface RelatableDocument {
2
+ id: string;
3
+ /** UUID fiscal (CFDI), cuando ya está timbrado. */
4
+ uuid?: string;
5
+ folio?: string;
6
+ /** Texto libre para mostrar («Factura A-120 · Cliente X»). */
7
+ label?: string;
8
+ total?: number;
9
+ }
10
+ export interface RelationTypeOption {
11
+ /** Clave que viaja en `relation_type` (p. ej. `"01"`). */
12
+ value: string;
13
+ label: string;
14
+ }
15
+ /** Un documento relacionado: lo que el formulario guarda. */
16
+ export interface RelatedDocument {
17
+ related_document_id: string;
18
+ relation_type: string;
19
+ /** Solo para mostrar. */
20
+ uuid?: string;
21
+ folio?: string;
22
+ label?: string;
23
+ }
24
+ export interface RelateDocumentsProps {
25
+ value: RelatedDocument[];
26
+ onChange: (value: RelatedDocument[]) => void;
27
+ /** Catálogo de tipos de relación (lo aporta el addon). */
28
+ relationTypes: RelationTypeOption[];
29
+ /** Búsqueda de documentos origen. Estable (useCallback). */
30
+ search: (q: string, signal: AbortSignal) => Promise<RelatableDocument[]>;
31
+ /** Tipo preseleccionado al agregar. Default: el primero del catálogo. */
32
+ defaultRelationType?: string;
33
+ /** Máximo de documentos relacionados. Sin tope por defecto. */
34
+ max?: number;
35
+ disabled?: boolean;
36
+ placeholder?: string;
37
+ }
38
+ export declare function RelateDocuments({ value, onChange, relationTypes, search, defaultRelationType, max, disabled, placeholder, }: RelateDocumentsProps): import("react").JSX.Element;
39
+ /** Valor a enviar al servidor: solo los datos, sin los campos de visualización. */
40
+ export declare function serializeRelatedDocuments(value: RelatedDocument[]): Array<Pick<RelatedDocument, 'related_document_id' | 'relation_type'>>;
41
+ //# sourceMappingURL=relate-documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relate-documents.d.ts","sourceRoot":"","sources":["../../src/business/relate-documents.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,iBAAiB;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IAC/B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC5B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;IACrB,yBAAyB;IACzB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,eAAe,EAAE,CAAA;IACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,IAAI,CAAA;IAC5C,0DAA0D;IAC1D,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,4DAA4D;IAC5D,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IACxE,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAMD,wBAAgB,eAAe,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,aAAa,EACb,MAAM,EACN,mBAAmB,EACnB,GAAG,EACH,QAAQ,EACR,WAAW,GACd,EAAE,oBAAoB,+BA+GtB;AAED,mFAAmF;AACnF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,qBAAqB,GAAG,eAAe,CAAC,CAAC,CAEzI"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // RelateDocuments — relaciona uno o más documentos origen (CFDI) desde un
3
+ // formulario: nota de crédito, devolución, sustitución. El valor es DATO
4
+ // (`related_document_id`, `relation_type`, UUID/folio para mostrar), no un flujo
5
+ // de un solo módulo. El SDK no embebe catálogos fiscales: los tipos de relación
6
+ // (p. ej. SAT 01/03/04) llegan por la prop `relationTypes`.
7
+ import { useState } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Link2, Search, X } from 'lucide-react';
10
+ import { Badge, Button, Input } from '@asteby/metacore-ui';
11
+ import { useAsyncSearch } from './use-async-search';
12
+ function docTitle(d) {
13
+ return d.label || d.folio || d.uuid || d.id || '';
14
+ }
15
+ export function RelateDocuments({ value, onChange, relationTypes, search, defaultRelationType, max, disabled, placeholder, }) {
16
+ const { t } = useTranslation();
17
+ const [text, setText] = useState('');
18
+ const [pendingType, setPendingType] = useState(defaultRelationType ?? relationTypes[0]?.value ?? '');
19
+ const { results, loading, error } = useAsyncSearch(text, search, { minChars: 2 });
20
+ const full = max != null && value.length >= max;
21
+ const typeLabel = (v) => relationTypes.find((r) => r.value === v)?.label ?? v;
22
+ const add = (d) => {
23
+ if (full || value.some((x) => x.related_document_id === d.id))
24
+ return;
25
+ onChange([
26
+ ...value,
27
+ { related_document_id: d.id, relation_type: pendingType, uuid: d.uuid, folio: d.folio, label: d.label },
28
+ ]);
29
+ setText('');
30
+ };
31
+ const remove = (id) => onChange(value.filter((x) => x.related_document_id !== id));
32
+ const setType = (id, relation_type) => onChange(value.map((x) => (x.related_document_id === id ? { ...x, relation_type } : x)));
33
+ const selectCls = 'h-9 rounded-md border bg-background px-2 text-sm';
34
+ return (_jsxs("div", { "data-slot": "relate-documents", className: "space-y-2", children: [value.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border", children: value.map((d) => (_jsxs("li", { className: "flex flex-wrap items-center gap-2 px-2 py-1.5", children: [_jsx(Link2, { className: "size-4 text-muted-foreground", "aria-hidden": true }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: docTitle({ folio: d.folio, label: d.label, id: d.related_document_id }) }), d.uuid && _jsx("p", { className: "break-all font-mono text-xs text-muted-foreground", children: d.uuid })] }), _jsxs("select", { className: selectCls, disabled: disabled, value: d.relation_type, "aria-label": t('relateDocuments.type', { defaultValue: 'Tipo de relación' }), onChange: (e) => setType(d.related_document_id, e.target.value), children: [!relationTypes.some((r) => r.value === d.relation_type) && (_jsx("option", { value: d.relation_type, children: typeLabel(d.relation_type) })), relationTypes.map((r) => (_jsx("option", { value: r.value, children: r.label }, r.value)))] }), !disabled && (_jsx(Button, { type: "button", size: "icon", variant: "ghost", onClick: () => remove(d.related_document_id), "aria-label": t('relateDocuments.remove', { defaultValue: 'Quitar relación' }), children: _jsx(X, { className: "size-4" }) }))] }, d.related_document_id))) })), !full && !disabled && (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-2.5 size-4 text-muted-foreground", "aria-hidden": true }), _jsx(Input, { className: "pl-8", value: text, placeholder: placeholder ?? t('relateDocuments.placeholder', { defaultValue: 'Buscar por folio o UUID' }), onChange: (e) => setText(e.target.value) })] }), _jsx("select", { className: selectCls, value: pendingType, "aria-label": t('relateDocuments.newType', { defaultValue: 'Tipo para el documento nuevo' }), onChange: (e) => setPendingType(e.target.value), children: relationTypes.map((r) => (_jsx("option", { value: r.value, children: r.label }, r.value))) })] }), loading && _jsx("p", { className: "text-sm text-muted-foreground", children: t('common.searching', { defaultValue: 'Buscando…' }) }), error && !loading && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: t('relateDocuments.error', { defaultValue: 'No se pudo buscar documentos. Inténtalo de nuevo.' }) })), !loading && !error && text.trim().length >= 2 && results.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: t('relateDocuments.empty', { defaultValue: 'No encontramos ese documento.' }) })), results.length > 0 && (_jsx("ul", { role: "listbox", className: "max-h-64 divide-y overflow-auto rounded-md border", children: results.map((d) => {
35
+ const already = value.some((x) => x.related_document_id === d.id);
36
+ return (_jsx("li", { role: "option", "aria-selected": already, children: _jsxs("button", { type: "button", disabled: already, className: "flex w-full items-center justify-between gap-2 px-2 py-1.5 text-left hover:bg-accent disabled:opacity-50", onClick: () => add(d), children: [_jsxs("span", { children: [_jsx("span", { className: "block text-sm font-medium", children: docTitle(d) }), d.uuid && _jsx("span", { className: "block break-all font-mono text-xs text-muted-foreground", children: d.uuid })] }), already && _jsx(Badge, { variant: "muted", children: t('relateDocuments.added', { defaultValue: 'Agregado' }) })] }) }, d.id));
37
+ }) }))] }))] }));
38
+ }
39
+ /** Valor a enviar al servidor: solo los datos, sin los campos de visualización. */
40
+ export function serializeRelatedDocuments(value) {
41
+ return value.map(({ related_document_id, relation_type }) => ({ related_document_id, relation_type }));
42
+ }
@@ -0,0 +1,36 @@
1
+ export interface VehicleResult {
2
+ id: string;
3
+ /** Placa tal como la capturó el taller (sin formato hardcodeado por país). */
4
+ plate?: string;
5
+ vin?: string;
6
+ make?: string;
7
+ model?: string;
8
+ year?: number | string;
9
+ color?: string;
10
+ /** Propietario, cuando el host lo expone. */
11
+ customer_id?: string;
12
+ customer_name?: string;
13
+ }
14
+ export interface VehiclePickerProps {
15
+ value: VehicleResult | null;
16
+ /** Evento único de cambio: vehículo elegido/creado, o `null` al limpiar. */
17
+ onChange: (vehicle: VehicleResult | null) => void;
18
+ /** Búsqueda por placa o VIN. Estable (useCallback). */
19
+ search: (q: string, signal: AbortSignal) => Promise<VehicleResult[]>;
20
+ /** Modelo del kernel para el alta rápida. Default `Vehicle`. */
21
+ model?: string;
22
+ /** Endpoint CRUD del alta. Default `/data/<model>/me`. */
23
+ endpoint?: string;
24
+ /** Convierte el registro guardado a `VehicleResult`. Default: id, placa, VIN, marca, modelo, año. */
25
+ mapRecord?: (rec: Record<string, unknown>) => VehicleResult;
26
+ /** Override de permiso (default `<model>.create` con useCan). */
27
+ canCreate?: boolean;
28
+ /** Valores iniciales del alta (p. ej. `customer_id` del cliente ya elegido). */
29
+ createDefaults?: Record<string, unknown>;
30
+ disabled?: boolean;
31
+ placeholder?: string;
32
+ }
33
+ /** Línea corta de un vehículo: «Nissan Versa 2019 · ABC-123 · VIN…». */
34
+ export declare function describeVehicle(v: VehicleResult): string;
35
+ export declare function VehiclePicker({ value, onChange, search, model, endpoint, mapRecord, canCreate, createDefaults, disabled, placeholder, }: VehiclePickerProps): import("react").JSX.Element;
36
+ //# sourceMappingURL=vehicle-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vehicle-picker.d.ts","sourceRoot":"","sources":["../../src/business/vehicle-picker.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,aAAa,GAAG,IAAI,CAAA;IAC3B,4EAA4E;IAC5E,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,KAAK,IAAI,CAAA;IACjD,uDAAuD;IACvD,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IACpE,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qGAAqG;IACrG,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,aAAa,CAAA;IAC3D,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAGxD;AAED,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,KAAiB,EACjB,QAAQ,EACR,SAAS,EACT,SAAS,EACT,cAAc,EACd,QAAQ,EACR,WAAW,GACd,EAAE,kBAAkB,+BAmHpB"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // VehiclePicker + alta rápida — busca por placa o VIN (y marca/modelo/cliente que
3
+ // el host decida indexar) y reutiliza el <CreateRecordDialog> dinámico del modelo
4
+ // vehículo para el alta rápida. Mismo contrato que <CustomerPicker>: controlado
5
+ // (`value` / `onChange`), `search` inyectado por el host, permisos con useCan
6
+ // (sin <PermissionsProvider> no bloquea nada: el backend autoriza).
7
+ import { useCallback, useState } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Car, Plus, Search, X } from 'lucide-react';
10
+ import { Button, Input } from '@asteby/metacore-ui';
11
+ import { CreateRecordDialog } from '../dialogs/create-record-dialog';
12
+ import { useApi } from '../api-context';
13
+ import { useCan } from '../permissions-context';
14
+ import { useAsyncSearch } from './use-async-search';
15
+ import { EmptyState } from './feedback';
16
+ function snake(model) {
17
+ return model.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
18
+ }
19
+ const str = (v) => (typeof v === 'string' && v ? v : undefined);
20
+ /** Línea corta de un vehículo: «Nissan Versa 2019 · ABC-123 · VIN…». */
21
+ export function describeVehicle(v) {
22
+ const unit = [v.make, v.model, v.year].filter((x) => x != null && x !== '').join(' ');
23
+ return [unit, v.plate, v.vin].filter(Boolean).join(' · ');
24
+ }
25
+ export function VehiclePicker({ value, onChange, search, model = 'Vehicle', endpoint, mapRecord, canCreate, createDefaults, disabled, placeholder, }) {
26
+ const { t } = useTranslation();
27
+ const api = useApi();
28
+ const can = useCan();
29
+ const [text, setText] = useState('');
30
+ const [creating, setCreating] = useState(false);
31
+ const { results, loading, error } = useAsyncSearch(text, search, { minChars: 2 });
32
+ const mayCreate = canCreate ?? can(`${snake(model)}.create`);
33
+ const base = endpoint ?? `/data/${model}/me`;
34
+ const pick = (v) => {
35
+ onChange(v);
36
+ setText('');
37
+ };
38
+ const toResult = useCallback((rec) => mapRecord
39
+ ? mapRecord(rec)
40
+ : {
41
+ id: String(rec.id ?? ''),
42
+ plate: str(rec.plate),
43
+ vin: str(rec.vin),
44
+ make: str(rec.make),
45
+ model: str(rec.model),
46
+ year: typeof rec.year === 'number' || typeof rec.year === 'string' ? rec.year : undefined,
47
+ color: str(rec.color),
48
+ customer_id: str(rec.customer_id),
49
+ }, [mapRecord]);
50
+ if (value) {
51
+ return (_jsxs("div", { "data-slot": "vehicle-picker", className: "flex items-start justify-between gap-2 rounded-md border p-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Car, { className: "mt-0.5 size-4 text-muted-foreground", "aria-hidden": true }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: value.plate || value.vin || t('vehiclePicker.noPlate', { defaultValue: 'Sin placa' }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: [[value.make, value.model, value.year].filter((x) => x != null && x !== '').join(' '), value.vin && value.plate ? value.vin : '', value.customer_name]
52
+ .filter(Boolean)
53
+ .join(' · ') })] })] }), !disabled && (_jsx(Button, { type: "button", size: "icon", variant: "ghost", onClick: () => onChange(null), "aria-label": t('vehiclePicker.clear', { defaultValue: 'Quitar vehículo' }), children: _jsx(X, { className: "size-4" }) }))] }));
54
+ }
55
+ return (_jsxs("div", { "data-slot": "vehicle-picker", className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-2.5 size-4 text-muted-foreground", "aria-hidden": true }), _jsx(Input, { className: "pl-8", disabled: disabled, value: text, placeholder: placeholder ?? t('vehiclePicker.placeholder', { defaultValue: 'Placa o VIN' }), onChange: (e) => setText(e.target.value) })] }), mayCreate && (_jsx(Button, { type: "button", variant: "outline", size: "icon", disabled: disabled, onClick: () => setCreating(true), "aria-label": t('vehiclePicker.create', { defaultValue: 'Nuevo vehículo' }), children: _jsx(Plus, { className: "size-4" }) }))] }), loading && _jsx("p", { className: "text-sm text-muted-foreground", children: t('common.searching', { defaultValue: 'Buscando…' }) }), error && !loading && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: t('vehiclePicker.error', { defaultValue: 'No se pudo buscar vehículos. Inténtalo de nuevo.' }) })), !loading && !error && text.trim().length >= 2 && results.length === 0 && (_jsx(EmptyState, { title: t('vehiclePicker.empty', { defaultValue: 'No encontramos ese vehículo' }), action: mayCreate ? { label: t('vehiclePicker.createAction', { defaultValue: 'Crear vehículo' }), onClick: () => setCreating(true) } : undefined })), results.length > 0 && (_jsx("ul", { role: "listbox", className: "max-h-64 divide-y overflow-auto rounded-md border", children: results.map((v) => (_jsx("li", { role: "option", "aria-selected": false, children: _jsxs("button", { type: "button", className: "flex w-full flex-col px-2 py-1.5 text-left hover:bg-accent", onClick: () => pick(v), children: [_jsx("span", { className: "text-sm font-medium", children: v.plate || v.vin || v.id }), _jsx("span", { className: "text-xs text-muted-foreground", children: [describeVehicle({ ...v, plate: undefined }), v.customer_name].filter(Boolean).join(' · ') })] }) }, v.id))) })), creating && (_jsx(CreateRecordDialog, { modelKey: model, open: creating, onOpenChange: setCreating, endpoint: base, defaults: createDefaults ?? (text.trim() ? { plate: text.trim() } : undefined), onCreate: async (data) => {
56
+ const res = await api.post(base, data);
57
+ const rec = (res.data?.data ?? res.data);
58
+ pick(toResult(rec));
59
+ return rec.id != null ? { id: String(rec.id) } : undefined;
60
+ } }))] }));
61
+ }
@@ -0,0 +1,29 @@
1
+ export interface CfdiStampResult {
2
+ fiscalUuid?: string;
3
+ pdfUrl?: string;
4
+ xmlUrl?: string;
5
+ /** Optional human folio (`serie-folio`) when the server sends it. */
6
+ folio?: string;
7
+ documentId?: string;
8
+ }
9
+ /**
10
+ * Reads a stamp result out of a successful action response body. Looks at
11
+ * `body.data`, `body.result` and the body itself. Returns `undefined` unless
12
+ * the payload carries `fiscal_uuid` and/or `pdf_url` / `xml_url` — a response
13
+ * without those keys is never turned into a fiscal result.
14
+ */
15
+ export declare function extractStampResult(body: unknown): CfdiStampResult | undefined;
16
+ export interface CfdiStampPanelProps {
17
+ result: CfdiStampResult;
18
+ className?: string;
19
+ }
20
+ export declare function CfdiStampPanel({ result, className }: CfdiStampPanelProps): import("react").JSX.Element;
21
+ export interface CfdiStampResultDialogProps {
22
+ open: boolean;
23
+ onOpenChange: (open: boolean) => void;
24
+ result: CfdiStampResult;
25
+ title?: string;
26
+ }
27
+ /** Dialog shell used by the action modals after a successful stamp. */
28
+ export declare function CfdiStampResultDialog({ open, onOpenChange, result, title }: CfdiStampResultDialogProps): import("react").JSX.Element;
29
+ //# sourceMappingURL=cfdi-stamp-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cfdi-stamp-panel.d.ts","sourceRoot":"","sources":["../src/cfdi-stamp-panel.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,eAAe;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAOD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAsB7E;AAED,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,eAAe,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,mBAAmB,+BAkDxE;AAED,MAAM,WAAW,0BAA0B;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,MAAM,EAAE,eAAe,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,0BAA0B,+BAkBtG"}
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // CfdiStampPanel — human-readable result of stamping (timbrar) a CFDI.
3
+ //
4
+ // A stamp action answers `{ document_id, fiscal_uuid, pdf_url, xml_url }`. The
5
+ // dialog used to dump that JSON; this panel shows the fiscal UUID as text and
6
+ // the PDF / XML as download links instead (#1024, FAC-3 / PIT-051). The SDK
7
+ // invents nothing: only the keys the server actually returned are rendered.
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Button, } from '@asteby/metacore-ui/primitives';
10
+ import { CheckCircle2, FileCode2, FileText } from 'lucide-react';
11
+ const str = (v) => typeof v === 'string' && v.trim() ? v.trim() : undefined;
12
+ const isSafeUrl = (u) => /^(https?:)?\/\//i.test(u) || u.startsWith('/') || u.startsWith('blob:');
13
+ /**
14
+ * Reads a stamp result out of a successful action response body. Looks at
15
+ * `body.data`, `body.result` and the body itself. Returns `undefined` unless
16
+ * the payload carries `fiscal_uuid` and/or `pdf_url` / `xml_url` — a response
17
+ * without those keys is never turned into a fiscal result.
18
+ */
19
+ export function extractStampResult(body) {
20
+ if (!body || typeof body !== 'object')
21
+ return undefined;
22
+ const b = body;
23
+ const candidates = [b.data, b.result, b];
24
+ for (const c of candidates) {
25
+ if (!c || typeof c !== 'object' || Array.isArray(c))
26
+ continue;
27
+ const o = c;
28
+ const fiscalUuid = str(o.fiscal_uuid) ?? str(o.fiscalUuid) ?? str(o.uuid);
29
+ const pdf = str(o.pdf_url) ?? str(o.pdfUrl);
30
+ const xml = str(o.xml_url) ?? str(o.xmlUrl);
31
+ // A bare `uuid` is too generic to call a fiscal result on its own.
32
+ const hasFiscal = !!str(o.fiscal_uuid) || !!str(o.fiscalUuid) || !!pdf || !!xml;
33
+ if (!hasFiscal)
34
+ continue;
35
+ return {
36
+ fiscalUuid,
37
+ pdfUrl: pdf && isSafeUrl(pdf) ? pdf : undefined,
38
+ xmlUrl: xml && isSafeUrl(xml) ? xml : undefined,
39
+ folio: str(o.folio) ?? str(o.number),
40
+ documentId: str(o.document_id) ?? str(o.documentId),
41
+ };
42
+ }
43
+ return undefined;
44
+ }
45
+ export function CfdiStampPanel({ result, className }) {
46
+ const { t } = useTranslation();
47
+ return (_jsxs("div", { "data-slot": "cfdi-stamp-panel", className: `space-y-4 ${className ?? ''}`, children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-emerald-600 dark:text-emerald-400", children: [_jsx(CheckCircle2, { className: "size-5", "aria-hidden": true }), t('cfdi.stamp.success', { defaultValue: 'CFDI timbrado correctamente' })] }), _jsxs("dl", { className: "space-y-2 rounded-md border bg-muted/40 p-3 text-sm", children: [result.folio && (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("dt", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t('cfdi.stamp.folio', { defaultValue: 'Folio' }) }), _jsx("dd", { children: result.folio })] })), result.fiscalUuid && (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("dt", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t('cfdi.stamp.uuid', { defaultValue: 'UUID fiscal' }) }), _jsx("dd", { className: "break-all font-mono", "data-slot": "cfdi-stamp-uuid", children: result.fiscalUuid })] }))] }), (result.pdfUrl || result.xmlUrl) && (_jsxs("div", { className: "flex flex-wrap gap-2", children: [result.pdfUrl && (_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs("a", { href: result.pdfUrl, target: "_blank", rel: "noopener noreferrer", "data-slot": "cfdi-stamp-pdf", children: [_jsx(FileText, { className: "mr-2 size-4", "aria-hidden": true }), t('cfdi.stamp.pdf', { defaultValue: 'Descargar PDF' })] }) })), result.xmlUrl && (_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs("a", { href: result.xmlUrl, target: "_blank", rel: "noopener noreferrer", "data-slot": "cfdi-stamp-xml", children: [_jsx(FileCode2, { className: "mr-2 size-4", "aria-hidden": true }), t('cfdi.stamp.xml', { defaultValue: 'Descargar XML' })] }) }))] }))] }));
48
+ }
49
+ /** Dialog shell used by the action modals after a successful stamp. */
50
+ export function CfdiStampResultDialog({ open, onOpenChange, result, title }) {
51
+ const { t } = useTranslation();
52
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title ?? t('cfdi.stamp.title', { defaultValue: 'Resultado del timbrado' }) }), _jsx(DialogDescription, { className: "sr-only", children: t('cfdi.stamp.desc', { defaultValue: 'UUID fiscal y archivos del CFDI timbrado' }) })] }), _jsx(CfdiStampPanel, { result: result }), _jsx(DialogFooter, { children: _jsx(Button, { onClick: () => onOpenChange(false), children: t('common.close', { defaultValue: 'Cerrar' }) }) })] }) }));
53
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
@@ -10,6 +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 { emitRecordMutation } from '../record-mutation-events';
13
14
  import { createContext, useCallback, useContext, useEffect, useId, useRef, useState } from 'react';
14
15
  import { useTranslation } from 'react-i18next';
15
16
  /** Model key of the open create/edit dialog — used to hide "+" on self-FK pickers
@@ -29,6 +30,7 @@ import { toastServerError, extractFieldErrors, localizeFieldIssue, localizeField
29
30
  import { DynamicSelectField, OptionLead, OptionThumb } from '../dynamic-select-field';
30
31
  import { DynamicMultiSelectField } from '../dynamic-multi-select-field';
31
32
  import { DynamicRelations } from '../dynamic-relations';
33
+ import { AuditInfo, readAuditMeta } from '../audit-info';
32
34
  import { useOptionsResolver } from '../use-options-resolver';
33
35
  import { getFieldRef, getVisibleWhen, evaluateVisibleWhen, ATTRIBUTE_CLASSES_KEY } from '../dynamic-form-schema';
34
36
  import { useAttributeClasses } from '../attribute-classes';
@@ -336,6 +338,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
336
338
  const { t } = useTranslation();
337
339
  const [modalMeta, setModalMeta] = useState(schema ? schema : null);
338
340
  const [relations, setRelations] = useState([]);
341
+ const [audit, setAudit] = useState(undefined);
339
342
  const [record, setRecord] = useState(null);
340
343
  const [formValues, setFormValues] = useState({});
341
344
  // Classes of the record's category (plus those its saved data carries), for
@@ -472,6 +475,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
472
475
  useEffect(() => {
473
476
  if (!open || mode === 'create' || !recordId) {
474
477
  setRelations([]);
478
+ setAudit(undefined);
475
479
  return;
476
480
  }
477
481
  let cancelled = false;
@@ -481,6 +485,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
481
485
  return;
482
486
  const meta = res.data?.data ?? res.data;
483
487
  const rels = Array.isArray(meta?.relations) ? meta.relations : [];
488
+ setAudit(readAuditMeta(meta?.audit));
484
489
  // Localize each panel header: the backend serves `label` as an
485
490
  // i18n key (addon bundle, loaded live) and the SDK renders it verbatim.
486
491
  setRelations(rels.map(rel => ({
@@ -621,6 +626,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
621
626
  if (isCreate && onCreate) {
622
627
  const created = await onCreate(payload);
623
628
  toast.success(modalMeta?.messages?.created || t('dynamic.create_success', { defaultValue: 'Registro creado correctamente' }));
629
+ emitRecordMutation(model, 'create');
624
630
  onSaved?.(created ?? undefined);
625
631
  onOpenChange(false);
626
632
  return;
@@ -628,6 +634,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
628
634
  if (!isCreate && recordId && onUpdate) {
629
635
  const updated = await onUpdate(String(recordId), payload);
630
636
  toast.success(modalMeta?.messages?.updated || t('dynamic.update_success', { defaultValue: 'Guardado correctamente' }));
637
+ emitRecordMutation(model, 'update');
631
638
  onSaved?.(updated ?? undefined);
632
639
  onOpenChange(false);
633
640
  return;
@@ -652,6 +659,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
652
659
  ? t('dynamic.create_success', { defaultValue: 'Registro creado correctamente' })
653
660
  : t('dynamic.update_success', { defaultValue: 'Guardado correctamente' })));
654
661
  // Hand the persisted record back so callers can auto-select it.
662
+ emitRecordMutation(model, isCreate ? 'create' : 'update');
655
663
  onSaved?.(res.data?.data ?? res.data ?? undefined);
656
664
  onOpenChange(false);
657
665
  }
@@ -675,6 +683,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
675
683
  setDeleting(true);
676
684
  try {
677
685
  await onDelete();
686
+ emitRecordMutation(model, 'delete');
678
687
  onOpenChange(false);
679
688
  }
680
689
  catch (err) {
@@ -739,7 +748,7 @@ export function DynamicRecordDialog({ open, onOpenChange, nestedInlineCreateSelf
739
748
  return (_jsx(RecordDialogModelContext.Provider, { value: model, children: _jsx(Dialog, { open: open, onOpenChange: onOpenChange, nestedInlineCreateSelf: nestedInlineCreateSelf, 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, "data-aby-record-form": "", "data-aby-model": model, "data-aby-mode": mode, onSubmit: handleSubmit, className: "grid gap-y-4", children: [isSteps && (_jsx(WizardProgress, { groups: groups, stepIndex: clampedStep, onStepClick: i => setStepIndex(i) })), (isSteps ? [groups[clampedStep]] : groups).map(group =>
740
749
  // An assisted step IS the interview: no form inputs, the
741
750
  // provider asks what it needs and fills the fields.
742
- isSteps && isEditable && group.assist ? (_jsx(AssistInterview, { assist: group.assist, values: formValues, eyebrow: group.title, autoStart: group.assist.trigger !== 'button', onApply: fields => setFormValues(prev => ({ ...prev, ...fields })) }, group.key)) : (_jsxs(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) }), isEditable && group.assist && (_jsx(AssistInterview, { assist: group.assist, values: formValues, eyebrow: group.title, autoStart: group.assist.trigger === 'auto', onApply: fields => setFormValues(prev => ({ ...prev, ...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] }))] })] })] }) }) }));
751
+ isSteps && isEditable && group.assist ? (_jsx(AssistInterview, { assist: group.assist, values: formValues, eyebrow: group.title, autoStart: group.assist.trigger !== 'button', onApply: fields => setFormValues(prev => ({ ...prev, ...fields })) }, group.key)) : (_jsxs(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) }), isEditable && group.assist && (_jsx(AssistInterview, { assist: group.assist, values: formValues, eyebrow: group.title, autoStart: group.assist.trigger === 'auto', onApply: fields => setFormValues(prev => ({ ...prev, ...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 }) })), !isCreate && record && audit && (_jsx(AuditInfo, { record: record, audit: audit, timeZone: timeZone, className: "mt-6 border-t pt-3" }))] }) }) }) })) : 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] }))] })] })] }) }) }));
743
752
  }
744
753
  function LoadingSkeleton() {
745
754
  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))) }));
@@ -0,0 +1,23 @@
1
+ import { type DocumentLinesGridProps } from './business/line-items-editor';
2
+ import type { DocumentFormsManifest, TableMetadata } from './types';
3
+ export interface DocumentFormDialogProps {
4
+ open: boolean;
5
+ onOpenChange: (open: boolean) => void;
6
+ /** Modelo en el que se da de alta (p. ej. `fiscal_documents`). */
7
+ model: string;
8
+ /** Endpoint de alta. Default `/dynamic/<model>`. */
9
+ endpoint?: string;
10
+ forms: DocumentFormsManifest;
11
+ /** Tipo preseleccionado (se salta el selector). */
12
+ initialType?: string;
13
+ /** Se llama con el registro creado. */
14
+ onSaved?: (record?: unknown) => void;
15
+ /** Buscador de productos para el editor de renglones (mismo contrato que ProductPicker). */
16
+ searchProducts?: DocumentLinesGridProps['search'];
17
+ /** Moneda ISO para el editor de renglones (default: la de la org). */
18
+ currency?: string;
19
+ }
20
+ /** Manifest efectivo: la prop gana sobre el metadata; sin tipos devuelve undefined. */
21
+ export declare function resolveDocumentForms(metadata?: Pick<TableMetadata, 'document_forms'> | null, override?: DocumentFormsManifest | null): DocumentFormsManifest | undefined;
22
+ export declare function DocumentFormDialog({ open, onOpenChange, model, endpoint, forms, initialType, onSaved, searchProducts, currency, }: DocumentFormDialogProps): import("react").JSX.Element;
23
+ //# sourceMappingURL=document-form-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-form-dialog.d.ts","sourceRoot":"","sources":["../src/document-form-dialog.tsx"],"names":[],"mappings":"AA4BA,OAAO,EAEH,KAAK,sBAAsB,EAE9B,MAAM,8BAA8B,CAAA;AAYrC,OAAO,KAAK,EAGR,qBAAqB,EAErB,aAAa,EAChB,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,qBAAqB,CAAA;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uCAAuC;IACvC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACpC,4FAA4F;IAC5F,cAAc,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IACjD,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAChC,QAAQ,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,IAAI,EACvD,QAAQ,CAAC,EAAE,qBAAqB,GAAG,IAAI,GACxC,qBAAqB,GAAG,SAAS,CAGnC;AAUD,wBAAgB,kBAAkB,CAAC,EAC/B,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,OAAO,EACP,cAAc,EACd,QAAQ,GACX,EAAE,uBAAuB,+BAmQzB"}