@asteby/metacore-runtime-react 4.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 (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +201 -0
  3. package/README.md +59 -0
  4. package/dist/action-modal-dispatcher.d.ts +4 -0
  5. package/dist/action-modal-dispatcher.d.ts.map +1 -0
  6. package/dist/action-modal-dispatcher.js +123 -0
  7. package/dist/addon-loader.d.ts +27 -0
  8. package/dist/addon-loader.d.ts.map +1 -0
  9. package/dist/addon-loader.js +73 -0
  10. package/dist/api-context.d.ts +40 -0
  11. package/dist/api-context.d.ts.map +1 -0
  12. package/dist/api-context.js +25 -0
  13. package/dist/capability-gate.d.ts +29 -0
  14. package/dist/capability-gate.d.ts.map +1 -0
  15. package/dist/capability-gate.js +43 -0
  16. package/dist/dialogs/_primitives.d.ts +29 -0
  17. package/dist/dialogs/_primitives.d.ts.map +1 -0
  18. package/dist/dialogs/_primitives.js +35 -0
  19. package/dist/dialogs/dynamic-record.d.ts +11 -0
  20. package/dist/dialogs/dynamic-record.d.ts.map +1 -0
  21. package/dist/dialogs/dynamic-record.js +377 -0
  22. package/dist/dialogs/export.d.ts +12 -0
  23. package/dist/dialogs/export.d.ts.map +1 -0
  24. package/dist/dialogs/export.js +146 -0
  25. package/dist/dialogs/import.d.ts +11 -0
  26. package/dist/dialogs/import.d.ts.map +1 -0
  27. package/dist/dialogs/import.js +128 -0
  28. package/dist/dynamic-columns-shim.d.ts +25 -0
  29. package/dist/dynamic-columns-shim.d.ts.map +1 -0
  30. package/dist/dynamic-columns-shim.js +1 -0
  31. package/dist/dynamic-form.d.ts +12 -0
  32. package/dist/dynamic-form.d.ts.map +1 -0
  33. package/dist/dynamic-form.js +51 -0
  34. package/dist/dynamic-icon.d.ts +6 -0
  35. package/dist/dynamic-icon.d.ts.map +1 -0
  36. package/dist/dynamic-icon.js +11 -0
  37. package/dist/dynamic-table.d.ts +22 -0
  38. package/dist/dynamic-table.d.ts.map +1 -0
  39. package/dist/dynamic-table.js +516 -0
  40. package/dist/i18n-provider.d.ts +16 -0
  41. package/dist/i18n-provider.d.ts.map +1 -0
  42. package/dist/i18n-provider.js +16 -0
  43. package/dist/index.d.ts +18 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +21 -0
  46. package/dist/metadata-cache.d.ts +42 -0
  47. package/dist/metadata-cache.d.ts.map +1 -0
  48. package/dist/metadata-cache.js +71 -0
  49. package/dist/navigation-builder.d.ts +34 -0
  50. package/dist/navigation-builder.d.ts.map +1 -0
  51. package/dist/navigation-builder.js +45 -0
  52. package/dist/options-context.d.ts +8 -0
  53. package/dist/options-context.d.ts.map +1 -0
  54. package/dist/options-context.js +5 -0
  55. package/dist/slot.d.ts +32 -0
  56. package/dist/slot.d.ts.map +1 -0
  57. package/dist/slot.js +45 -0
  58. package/dist/types.d.ts +114 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +1 -0
  61. package/package.json +67 -0
  62. package/src/action-modal-dispatcher.tsx +275 -0
  63. package/src/addon-loader.tsx +111 -0
  64. package/src/api-context.tsx +55 -0
  65. package/src/capability-gate.tsx +69 -0
  66. package/src/dialogs/_primitives.tsx +114 -0
  67. package/src/dialogs/dynamic-record.tsx +770 -0
  68. package/src/dialogs/export.tsx +339 -0
  69. package/src/dialogs/import.tsx +404 -0
  70. package/src/dynamic-columns-shim.ts +36 -0
  71. package/src/dynamic-form.tsx +108 -0
  72. package/src/dynamic-icon.tsx +15 -0
  73. package/src/dynamic-table.tsx +766 -0
  74. package/src/i18n-provider.tsx +33 -0
  75. package/src/index.ts +30 -0
  76. package/src/metadata-cache.ts +103 -0
  77. package/src/navigation-builder.tsx +77 -0
  78. package/src/options-context.tsx +11 -0
  79. package/src/slot.tsx +77 -0
  80. package/src/types.ts +112 -0
  81. package/tsconfig.json +16 -0
@@ -0,0 +1,146 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ExportDialog — lets users pick format (csv/json) + columns and kicks off
3
+ // either a sync download or an async export job (polled via /exports/:id/status).
4
+ // Ported from the ops starter. Axios-like client is provided by <ApiProvider>.
5
+ import { useState, useEffect, useCallback } from 'react';
6
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Label, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@asteby/metacore-ui/primitives';
7
+ import { Progress, RadioGroup, RadioGroupItem } from './_primitives';
8
+ import { toast } from 'sonner';
9
+ import { Download, ChevronDown, Loader2 } from 'lucide-react';
10
+ import { useApi } from '../api-context';
11
+ export function ExportDialog({ open, onOpenChange, model, metadata, currentFilters, hasActiveFilters, }) {
12
+ const api = useApi();
13
+ const [format, setFormat] = useState('csv');
14
+ const [exportAll, setExportAll] = useState(false);
15
+ const [selectedColumns, setSelectedColumns] = useState([]);
16
+ const [columnsOpen, setColumnsOpen] = useState(false);
17
+ const [exporting, setExporting] = useState(false);
18
+ const [progress, setProgress] = useState(0);
19
+ const [asyncJobId, setAsyncJobId] = useState(null);
20
+ useEffect(() => {
21
+ if (open && metadata?.columns) {
22
+ setSelectedColumns(metadata.columns
23
+ .filter(col => !col.hidden)
24
+ .map(col => col.key));
25
+ setFormat('csv');
26
+ setExportAll(false);
27
+ setColumnsOpen(false);
28
+ setExporting(false);
29
+ setProgress(0);
30
+ setAsyncJobId(null);
31
+ }
32
+ }, [open, metadata]);
33
+ const toggleColumn = useCallback((key) => {
34
+ setSelectedColumns((prev) => prev.includes(key)
35
+ ? prev.filter((k) => k !== key)
36
+ : [...prev, key]);
37
+ }, []);
38
+ const toggleAllColumns = useCallback(() => {
39
+ const visibleKeys = metadata.columns
40
+ .filter(col => !col.hidden)
41
+ .map(col => col.key);
42
+ if (selectedColumns.length === visibleKeys.length) {
43
+ setSelectedColumns([]);
44
+ }
45
+ else {
46
+ setSelectedColumns(visibleKeys);
47
+ }
48
+ }, [metadata, selectedColumns]);
49
+ useEffect(() => {
50
+ if (!asyncJobId)
51
+ return;
52
+ const interval = setInterval(async () => {
53
+ try {
54
+ const res = await api.get(`/exports/${asyncJobId}/status`);
55
+ const status = res.data?.data ?? res.data;
56
+ if (status.progress !== undefined) {
57
+ setProgress(status.progress);
58
+ }
59
+ if (status.status === 'completed') {
60
+ clearInterval(interval);
61
+ const downloadRes = await api.get(`/exports/${asyncJobId}/download`, { responseType: 'blob' });
62
+ triggerDownload(downloadRes.data, format);
63
+ setExporting(false);
64
+ setAsyncJobId(null);
65
+ toast.success('Exportación completada');
66
+ onOpenChange(false);
67
+ }
68
+ else if (status.status === 'failed') {
69
+ clearInterval(interval);
70
+ setExporting(false);
71
+ setAsyncJobId(null);
72
+ toast.error(status.error_message || 'Error al exportar');
73
+ }
74
+ }
75
+ catch {
76
+ clearInterval(interval);
77
+ setExporting(false);
78
+ setAsyncJobId(null);
79
+ toast.error('Error al verificar el estado de la exportación');
80
+ }
81
+ }, 2000);
82
+ return () => clearInterval(interval);
83
+ }, [asyncJobId, format, onOpenChange, api]);
84
+ const triggerDownload = (blob, fmt) => {
85
+ const url = window.URL.createObjectURL(blob);
86
+ const link = document.createElement('a');
87
+ link.href = url;
88
+ link.download = `${model}-export.${fmt === 'json' ? 'json' : 'csv'}`;
89
+ document.body.appendChild(link);
90
+ link.click();
91
+ document.body.removeChild(link);
92
+ window.URL.revokeObjectURL(url);
93
+ };
94
+ const handleExport = async () => {
95
+ if (selectedColumns.length === 0) {
96
+ toast.error('Selecciona al menos una columna para exportar');
97
+ return;
98
+ }
99
+ setExporting(true);
100
+ setProgress(0);
101
+ try {
102
+ const params = {
103
+ format,
104
+ columns: selectedColumns.join(','),
105
+ };
106
+ if (!exportAll && currentFilters) {
107
+ Object.entries(currentFilters).forEach(([key, value]) => {
108
+ if (value !== undefined && value !== '') {
109
+ params[key] = value;
110
+ }
111
+ });
112
+ }
113
+ const response = await api.get(`/data/${model}/export`, {
114
+ params,
115
+ responseType: 'blob',
116
+ validateStatus: () => true,
117
+ });
118
+ const contentType = response.headers?.['content-type'] || '';
119
+ if (contentType.includes('application/json')) {
120
+ const text = await response.data.text();
121
+ const json = JSON.parse(text);
122
+ if (json.async && json.job_id) {
123
+ setAsyncJobId(json.job_id);
124
+ setProgress(10);
125
+ toast.info(`Exportando ${json.total} registros...`);
126
+ }
127
+ else if (!json.success) {
128
+ setExporting(false);
129
+ toast.error(json.message || 'Error al exportar');
130
+ }
131
+ }
132
+ else {
133
+ triggerDownload(response.data, format);
134
+ setExporting(false);
135
+ toast.success('Exportación completada');
136
+ onOpenChange(false);
137
+ }
138
+ }
139
+ catch {
140
+ setExporting(false);
141
+ toast.error('Error al exportar los datos');
142
+ }
143
+ };
144
+ const visibleColumns = metadata?.columns?.filter(col => !col.hidden) ?? [];
145
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-md max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsxs(DialogTitle, { children: ["Exportar ", metadata.title] }), _jsx(DialogDescription, { children: "Selecciona el formato y las columnas a exportar." })] }), _jsx("div", { className: "flex-1 overflow-y-auto p-6 space-y-6", children: exporting ? (_jsxs("div", { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-muted-foreground text-center", children: "Exportando datos..." }), _jsx(Progress, { value: progress }), _jsx("p", { className: "text-xs text-muted-foreground text-center", children: progress > 0 ? `${Math.round(progress)}%` : 'Preparando...' })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-3", children: [_jsx(Label, { className: "text-sm font-medium", children: "Formato" }), _jsxs(RadioGroup, { value: format, onValueChange: (val) => setFormat(val), className: "flex gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(RadioGroupItem, { value: "csv", id: "format-csv" }), _jsx(Label, { htmlFor: "format-csv", className: "font-normal cursor-pointer", children: "CSV" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(RadioGroupItem, { value: "json", id: "format-json" }), _jsx(Label, { htmlFor: "format-json", className: "font-normal cursor-pointer", children: "JSON" })] })] })] }), hasActiveFilters && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "export-all", checked: exportAll, onCheckedChange: (checked) => setExportAll(checked === true) }), _jsx(Label, { htmlFor: "export-all", className: "font-normal cursor-pointer text-sm", children: "Exportar todos los registros (ignorar filtros)" })] })), _jsxs(Collapsible, { open: columnsOpen, onOpenChange: setColumnsOpen, children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "w-full justify-between px-0 hover:bg-transparent", children: [_jsxs("span", { className: "text-sm font-medium", children: ["Columnas (", selectedColumns.length, "/", visibleColumns.length, ")"] }), _jsx(ChevronDown, { className: `h-4 w-4 transition-transform ${columnsOpen ? 'rotate-180' : ''}` })] }) }), _jsxs(CollapsibleContent, { className: "space-y-2 pt-2", children: [_jsxs("div", { className: "flex items-center gap-2 pb-2 border-b", children: [_jsx(Checkbox, { id: "select-all-columns", checked: selectedColumns.length === visibleColumns.length, onCheckedChange: toggleAllColumns }), _jsx(Label, { htmlFor: "select-all-columns", className: "font-normal cursor-pointer text-sm", children: "Seleccionar todas" })] }), _jsx("div", { className: "grid grid-cols-2 gap-2 max-h-48 overflow-y-auto", children: visibleColumns.map(col => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `col-${col.key}`, checked: selectedColumns.includes(col.key), onCheckedChange: () => toggleColumn(col.key) }), _jsx(Label, { htmlFor: `col-${col.key}`, className: "font-normal cursor-pointer text-sm truncate", children: col.label })] }, col.key))) })] })] })] })) }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: exporting, children: "Cancelar" }), !exporting && (_jsxs(Button, { onClick: handleExport, disabled: selectedColumns.length === 0, children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), "Exportar"] })), exporting && (_jsxs(Button, { disabled: true, children: [_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" }), "Exportando..."] }))] })] }) }));
146
+ }
@@ -0,0 +1,11 @@
1
+ import type { TableMetadata } from '../types';
2
+ interface ImportDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ model: string;
6
+ metadata: TableMetadata;
7
+ onImported?: () => void;
8
+ }
9
+ export declare function ImportDialog({ open, onOpenChange, model, metadata, onImported, }: ImportDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/dialogs/import.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG7C,UAAU,iBAAiB;IACvB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,aAAa,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAoBD,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,UAAU,GACb,EAAE,iBAAiB,2CAwVnB"}
@@ -0,0 +1,128 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ImportDialog — three-step CSV/JSON import flow (upload → validate → import
3
+ // with per-row error report). Ported from the ops starter. Axios-like client
4
+ // is provided by <ApiProvider>.
5
+ import { useState, useEffect, useRef } from 'react';
6
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Input, Label, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@asteby/metacore-ui/primitives';
7
+ import { Progress } from './_primitives';
8
+ import { toast } from 'sonner';
9
+ import { FileDown, Loader2, Check, AlertCircle } from 'lucide-react';
10
+ import { useApi } from '../api-context';
11
+ export function ImportDialog({ open, onOpenChange, model, metadata, onImported, }) {
12
+ const api = useApi();
13
+ const [step, setStep] = useState('upload');
14
+ const [file, setFile] = useState(null);
15
+ const [validating, setValidating] = useState(false);
16
+ const [importing, setImporting] = useState(false);
17
+ const [validationResult, setValidationResult] = useState(null);
18
+ const [importResult, setImportResult] = useState(null);
19
+ const [progress, setProgress] = useState(0);
20
+ const fileInputRef = useRef(null);
21
+ useEffect(() => {
22
+ if (open) {
23
+ setStep('upload');
24
+ setFile(null);
25
+ setValidating(false);
26
+ setImporting(false);
27
+ setValidationResult(null);
28
+ setImportResult(null);
29
+ setProgress(0);
30
+ if (fileInputRef.current)
31
+ fileInputRef.current.value = '';
32
+ }
33
+ }, [open]);
34
+ const handleFileChange = (e) => {
35
+ const selectedFile = e.target.files?.[0] ?? null;
36
+ setFile(selectedFile);
37
+ };
38
+ const handleDownloadTemplate = async () => {
39
+ try {
40
+ const response = await api.get(`/data/${model}/export/template`, {
41
+ responseType: 'blob',
42
+ });
43
+ const url = window.URL.createObjectURL(response.data);
44
+ const link = document.createElement('a');
45
+ link.href = url;
46
+ link.download = `${model}-plantilla.csv`;
47
+ document.body.appendChild(link);
48
+ link.click();
49
+ document.body.removeChild(link);
50
+ window.URL.revokeObjectURL(url);
51
+ }
52
+ catch {
53
+ toast.error('Error al descargar la plantilla');
54
+ }
55
+ };
56
+ const handleValidate = async () => {
57
+ if (!file) {
58
+ toast.error('Selecciona un archivo para validar');
59
+ return;
60
+ }
61
+ setValidating(true);
62
+ try {
63
+ const formData = new FormData();
64
+ formData.append('file', file);
65
+ const res = await api.post(`/data/${model}/import/validate`, formData, {
66
+ headers: { 'Content-Type': 'multipart/form-data' },
67
+ });
68
+ const data = res.data?.data ?? res.data;
69
+ setValidationResult({
70
+ valid: data.valid ?? 0,
71
+ errors: data.errors ?? [],
72
+ });
73
+ setStep('validation');
74
+ }
75
+ catch (err) {
76
+ const message = err?.response?.data?.message || 'Error al validar el archivo';
77
+ toast.error(message);
78
+ }
79
+ finally {
80
+ setValidating(false);
81
+ }
82
+ };
83
+ const handleImport = async () => {
84
+ if (!file)
85
+ return;
86
+ setImporting(true);
87
+ setProgress(0);
88
+ try {
89
+ const formData = new FormData();
90
+ formData.append('file', file);
91
+ const res = await api.post(`/data/${model}/import`, formData, {
92
+ headers: { 'Content-Type': 'multipart/form-data' },
93
+ onUploadProgress: (progressEvent) => {
94
+ if (progressEvent.total) {
95
+ setProgress(Math.round((progressEvent.loaded / progressEvent.total) * 100));
96
+ }
97
+ },
98
+ });
99
+ const data = res.data?.data ?? res.data;
100
+ setImportResult({
101
+ created: data.created ?? 0,
102
+ errors: data.errors ?? [],
103
+ });
104
+ setStep('results');
105
+ if ((data.created ?? 0) > 0) {
106
+ onImported?.();
107
+ }
108
+ }
109
+ catch (err) {
110
+ const message = err?.response?.data?.message || 'Error al importar los datos';
111
+ toast.error(message);
112
+ }
113
+ finally {
114
+ setImporting(false);
115
+ setProgress(0);
116
+ }
117
+ };
118
+ const handleClose = () => {
119
+ onOpenChange(false);
120
+ };
121
+ const stepTitle = {
122
+ upload: 'Subir archivo',
123
+ validation: 'Validacion',
124
+ results: 'Resultados',
125
+ };
126
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden", children: [_jsxs(DialogHeader, { className: "p-6 pb-4 border-b shrink-0", children: [_jsxs(DialogTitle, { children: ["Importar ", metadata.title] }), _jsx(DialogDescription, { children: stepTitle[step] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto p-6", children: [step === 'upload' && (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsxs(Button, { variant: "link", className: "px-0 h-auto text-sm", onClick: handleDownloadTemplate, children: [_jsx(FileDown, { className: "h-4 w-4 mr-1" }), "Descargar plantilla CSV"] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Descarga la plantilla para asegurar el formato correcto." })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "import-file", className: "text-sm font-medium", children: "Archivo" }), _jsx(Input, { ref: fileInputRef, id: "import-file", type: "file", accept: ".csv,.json", onChange: handleFileChange, className: "cursor-pointer" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Formatos aceptados: CSV, JSON" })] })] })), step === 'validation' && validationResult && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { children: [_jsx("strong", { children: validationResult.valid }), " valido(s)"] })] }), validationResult.errors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: [_jsx("strong", { children: validationResult.errors.length }), " error(es)"] })] }))] }), validationResult.errors.length > 0 && (_jsx("div", { className: "border rounded-md max-h-60 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-16", children: "Fila" }), _jsx(TableHead, { children: "Campo" }), _jsx(TableHead, { children: "Error" })] }) }), _jsx(TableBody, { children: validationResult.errors.map((error, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: error.row }), _jsx(TableCell, { className: "text-sm", children: error.field }), _jsx(TableCell, { className: "text-sm text-destructive", children: error.message })] }, idx))) })] }) })), importing && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { value: progress }), _jsxs("p", { className: "text-xs text-muted-foreground text-center", children: ["Importando... ", progress > 0 ? `${progress}%` : ''] })] }))] })), step === 'results' && importResult && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [importResult.created > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { children: [_jsx("strong", { children: importResult.created }), " creado(s)"] })] })), importResult.errors.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { children: [_jsx("strong", { children: importResult.errors.length }), " error(es)"] })] }))] }), importResult.created > 0 && importResult.errors.length === 0 && (_jsxs("div", { className: "flex items-center gap-2 rounded-md bg-green-50 dark:bg-green-950/20 p-3 text-sm text-green-700 dark:text-green-400", children: [_jsx(Check, { className: "h-4 w-4 shrink-0" }), "Todos los registros fueron importados correctamente."] })), importResult.errors.length > 0 && (_jsx("div", { className: "border rounded-md max-h-60 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-16", children: "Fila" }), _jsx(TableHead, { children: "Campo" }), _jsx(TableHead, { children: "Error" })] }) }), _jsx(TableBody, { children: importResult.errors.map((error, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-xs", children: error.row }), _jsx(TableCell, { className: "text-sm", children: error.field }), _jsx(TableCell, { className: "text-sm text-destructive", children: error.message })] }, idx))) })] }) }))] }))] }), _jsxs(DialogFooter, { className: "p-4 border-t shrink-0", children: [step === 'upload' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: handleClose, children: "Cancelar" }), _jsxs(Button, { onClick: handleValidate, disabled: !file || validating, children: [validating && (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })), validating ? 'Validando...' : 'Validar'] })] })), step === 'validation' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => setStep('upload'), disabled: importing, children: "Atras" }), _jsxs(Button, { onClick: handleImport, disabled: importing ||
127
+ (validationResult !== null && validationResult.valid === 0), children: [importing && (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })), importing ? 'Importando...' : 'Importar'] })] })), step === 'results' && (_jsx(Button, { onClick: handleClose, children: "Cerrar" }))] })] }) }));
128
+ }
@@ -0,0 +1,25 @@
1
+ import type { ColumnDef } from '@tanstack/react-table';
2
+ import type { TableMetadata } from './types';
3
+ export interface FilterOption {
4
+ label: string;
5
+ value: string;
6
+ icon?: string;
7
+ color?: string;
8
+ }
9
+ export interface ColumnFilterConfig {
10
+ filterType: 'select' | 'boolean' | 'date_range' | 'number_range' | 'text' | string;
11
+ filterKey: string;
12
+ options: FilterOption[];
13
+ selectedValues: string[];
14
+ onFilterChange: (filterKey: string, values: string[]) => void;
15
+ loading?: boolean;
16
+ searchEndpoint?: string;
17
+ }
18
+ /** Signature for the host-provided `getDynamicColumns` factory. */
19
+ export type GetDynamicColumns = (metadata: TableMetadata, handleAction: (action: string, row: any) => void, t: (key: string, options?: any) => string, language: string, columnFilterConfigs: Map<string, ColumnFilterConfig>) => ColumnDef<any>[];
20
+ /** Signature for the host-provided `DynamicIcon` renderer. */
21
+ export type DynamicIconComponent = React.ComponentType<{
22
+ name: string;
23
+ className?: string;
24
+ }>;
25
+ //# sourceMappingURL=dynamic-columns-shim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-columns-shim.d.ts","sourceRoot":"","sources":["../src/dynamic-columns-shim.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IAC/B,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAA;IAClF,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,mEAAmE;AACnE,MAAM,MAAM,iBAAiB,GAAG,CAC5B,QAAQ,EAAE,aAAa,EACvB,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,EAChD,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,EACzC,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,KACnD,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;AAErB,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { ActionFieldDef } from './types';
2
+ export interface DynamicFormProps {
3
+ fields: ActionFieldDef[];
4
+ initialValues?: Record<string, any>;
5
+ onSubmit: (values: Record<string, any>) => void | Promise<void>;
6
+ onCancel?: () => void;
7
+ submitLabel?: string;
8
+ cancelLabel?: string;
9
+ disabled?: boolean;
10
+ }
11
+ export declare function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel, cancelLabel, disabled, }: DynamicFormProps): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=dynamic-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,GACnB,EAAE,gBAAgB,2CA+ClB"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Minimal standalone DynamicForm. Factored from the dynamic-record-dialog
3
+ // pattern + ActionFieldDef renderer so callers can reuse the form layout
4
+ // outside the full record-edit modal.
5
+ import { useEffect, useState } from 'react';
6
+ import { Input, Textarea, Label, Switch, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@asteby/metacore-ui/primitives';
7
+ export function DynamicForm({ fields, initialValues, onSubmit, onCancel, submitLabel = 'Guardar', cancelLabel = 'Cancelar', disabled = false, }) {
8
+ const [values, setValues] = useState({});
9
+ const [submitting, setSubmitting] = useState(false);
10
+ useEffect(() => {
11
+ const defaults = {};
12
+ for (const f of fields) {
13
+ defaults[f.key] = initialValues?.[f.key] ?? f.defaultValue ?? (f.type === 'boolean' ? false : '');
14
+ }
15
+ setValues(defaults);
16
+ }, [fields, initialValues]);
17
+ const update = (k, v) => setValues((prev) => ({ ...prev, [k]: v }));
18
+ const handleSubmit = async (e) => {
19
+ e.preventDefault();
20
+ for (const f of fields) {
21
+ if (f.required && !values[f.key] && values[f.key] !== false) {
22
+ alert(`${f.label} es requerido`);
23
+ return;
24
+ }
25
+ }
26
+ setSubmitting(true);
27
+ try {
28
+ await onSubmit(values);
29
+ }
30
+ finally {
31
+ setSubmitting(false);
32
+ }
33
+ };
34
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "grid gap-4", children: [fields.map((field) => (_jsxs("div", { className: "grid gap-2", children: [_jsxs(Label, { htmlFor: field.key, children: [field.label, field.required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] }), renderField(field, values[field.key], (v) => update(field.key, v))] }, field.key))), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: submitting || disabled, children: cancelLabel })), _jsx(Button, { type: "submit", disabled: submitting || disabled, children: submitLabel })] })] }));
35
+ }
36
+ function renderField(field, value, onChange) {
37
+ switch (field.type) {
38
+ case 'textarea':
39
+ return _jsx(Textarea, { id: field.key, value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
40
+ case 'select':
41
+ return (_jsxs(Select, { value: value || '', onValueChange: onChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: field.placeholder || 'Seleccionar...' }) }), _jsx(SelectContent, { children: field.options?.map((opt) => _jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })] }));
42
+ case 'boolean':
43
+ return _jsx(Switch, { id: field.key, checked: !!value, onCheckedChange: onChange });
44
+ case 'number':
45
+ return _jsx(Input, { id: field.key, type: "number", value: value ?? '', onChange: (e) => onChange(e.target.valueAsNumber || ''), placeholder: field.placeholder });
46
+ case 'date':
47
+ return _jsx(Input, { id: field.key, type: "date", value: value || '', onChange: (e) => onChange(e.target.value) });
48
+ default:
49
+ return _jsx(Input, { id: field.key, type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
50
+ }
51
+ }
@@ -0,0 +1,6 @@
1
+ export interface DynamicIconProps {
2
+ name: string;
3
+ className?: string;
4
+ }
5
+ export declare function DynamicIcon({ name, className }: DynamicIconProps): import("react/jsx-runtime").JSX.Element | null;
6
+ //# sourceMappingURL=dynamic-icon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-icon.d.ts","sourceRoot":"","sources":["../src/dynamic-icon.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,gBAAgB,kDAIhE"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Minimal DynamicIcon — resolves a lucide-react icon by name. Used across
3
+ // action modals and the default row-action menus. Hosts that need custom
4
+ // icon sets can override by shadowing this component via their own prop.
5
+ import * as icons from 'lucide-react';
6
+ export function DynamicIcon({ name, className }) {
7
+ const Icon = icons[name];
8
+ if (!Icon)
9
+ return null;
10
+ return _jsx(Icon, { className: className });
11
+ }
@@ -0,0 +1,22 @@
1
+ import { type ColumnDef } from '@tanstack/react-table';
2
+ import type { GetDynamicColumns } from './dynamic-columns-shim';
3
+ interface DynamicTableProps {
4
+ model: string;
5
+ endpoint?: string;
6
+ enableUrlSync?: boolean;
7
+ hiddenColumns?: string[];
8
+ onAction?: (action: string, row: any) => void;
9
+ refreshTrigger?: any;
10
+ defaultFilters?: Record<string, any>;
11
+ extraColumns?: ColumnDef<any>[];
12
+ /**
13
+ * Host-provided factory that turns metadata into TanStack column defs.
14
+ * Lives in the host because the rendered cells depend on the host's
15
+ * design system (Badge, Avatar, MediaGallery, phone flags, etc.).
16
+ * Optional — a sensible default maps each column to { accessorKey, header }.
17
+ */
18
+ getDynamicColumns?: GetDynamicColumns;
19
+ }
20
+ export declare function DynamicTable({ model, endpoint, enableUrlSync, hiddenColumns, onAction, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, }: DynamicTableProps): import("react/jsx-runtime").JSX.Element;
21
+ export {};
22
+ //# sourceMappingURL=dynamic-table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAQnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CA+oBnB"}