@asteby/metacore-runtime-react 29.2.11 → 29.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 29.2.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 0150e2f: ExportDialog localizes labels (core text + manifest i18n keys) and sends column_labels so CSV headers match the UI.
8
+
3
9
  ## 29.2.11
4
10
 
5
11
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/dialogs/export.tsx"],"names":[],"mappings":"AAqBA,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,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,GACnB,EAAE,iBAAiB,+BA0SnB"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/dialogs/export.tsx"],"names":[],"mappings":"AA8BA,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,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,wBAAgB,YAAY,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,GACnB,EAAE,iBAAiB,+BAwUnB"}
@@ -1,8 +1,17 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // ExportDialog — lets users pick format (csv/json) + columns and kicks off
3
3
  // either a sync download or an async export job (polled via /exports/:id/status).
4
- // Axios-like client is provided by <ApiProvider>.
5
- import { useState, useEffect, useCallback } from 'react';
4
+ //
5
+ // Labels work for BOTH host styles:
6
+ // - Core DefineTable with human text ("Especialidades") → shown as-is via
7
+ // t(label, { defaultValue: label })
8
+ // - Manifest i18n keys ("purchases.field.state") → resolved by the host i18n
9
+ // bundle the same way DynamicTable already translates column headers
10
+ // The localized labels are also sent to the API as `column_labels` so the CSV
11
+ // headers match what the user sees in the dialog (backend may not have the
12
+ // addon locale bundles that only live on the frontend).
13
+ import { useState, useEffect, useCallback, useMemo } from 'react';
14
+ import { useTranslation } from 'react-i18next';
6
15
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Button, Label, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@asteby/metacore-ui/primitives';
7
16
  import { Progress, RadioGroup, RadioGroupItem } from './_primitives';
8
17
  import { toast } from 'sonner';
@@ -10,6 +19,7 @@ import { Download, ChevronDown, Loader2 } from 'lucide-react';
10
19
  import { useApi } from '../api-context';
11
20
  export function ExportDialog({ open, onOpenChange, model, metadata, currentFilters, hasActiveFilters, }) {
12
21
  const api = useApi();
22
+ const { t, i18n } = useTranslation();
13
23
  const [format, setFormat] = useState('csv');
14
24
  const [exportAll, setExportAll] = useState(false);
15
25
  const [selectedColumns, setSelectedColumns] = useState([]);
@@ -17,11 +27,22 @@ export function ExportDialog({ open, onOpenChange, model, metadata, currentFilte
17
27
  const [exporting, setExporting] = useState(false);
18
28
  const [progress, setProgress] = useState(0);
19
29
  const [asyncJobId, setAsyncJobId] = useState(null);
30
+ const tr = useCallback((label, fallback) => {
31
+ const raw = (label && label.trim()) || fallback || '';
32
+ if (!raw)
33
+ return fallback || '';
34
+ return t(raw, { defaultValue: raw });
35
+ }, [t]);
36
+ const title = useMemo(() => tr(metadata.titleKey || metadata.title, metadata.title || model), [metadata, model, tr]);
37
+ const visibleColumns = useMemo(() => (metadata?.columns?.filter((col) => !col.hidden) ?? []).map((col) => ({
38
+ ...col,
39
+ displayLabel: tr(col.label, col.key),
40
+ })), [metadata, tr]);
20
41
  useEffect(() => {
21
42
  if (open && metadata?.columns) {
22
43
  setSelectedColumns(metadata.columns
23
- .filter(col => !col.hidden)
24
- .map(col => col.key));
44
+ .filter((col) => !col.hidden)
45
+ .map((col) => col.key));
25
46
  setFormat('csv');
26
47
  setExportAll(false);
27
48
  setColumnsOpen(false);
@@ -36,16 +57,14 @@ export function ExportDialog({ open, onOpenChange, model, metadata, currentFilte
36
57
  : [...prev, key]);
37
58
  }, []);
38
59
  const toggleAllColumns = useCallback(() => {
39
- const visibleKeys = metadata.columns
40
- .filter(col => !col.hidden)
41
- .map(col => col.key);
60
+ const visibleKeys = visibleColumns.map((col) => col.key);
42
61
  if (selectedColumns.length === visibleKeys.length) {
43
62
  setSelectedColumns([]);
44
63
  }
45
64
  else {
46
65
  setSelectedColumns(visibleKeys);
47
66
  }
48
- }, [metadata, selectedColumns]);
67
+ }, [visibleColumns, selectedColumns]);
49
68
  useEffect(() => {
50
69
  if (!asyncJobId)
51
70
  return;
@@ -99,9 +118,19 @@ export function ExportDialog({ open, onOpenChange, model, metadata, currentFilte
99
118
  setExporting(true);
100
119
  setProgress(0);
101
120
  try {
121
+ const columnLabels = {};
122
+ for (const col of visibleColumns) {
123
+ if (selectedColumns.includes(col.key)) {
124
+ columnLabels[col.key] = col.displayLabel;
125
+ }
126
+ }
102
127
  const params = {
103
128
  format,
104
129
  columns: selectedColumns.join(','),
130
+ // Localized headers so CSV matches the dialog (core text OR
131
+ // manifest i18n keys resolved by the host frontend).
132
+ column_labels: JSON.stringify(columnLabels),
133
+ lang: i18n.language || 'es',
105
134
  };
106
135
  if (!exportAll && currentFilters) {
107
136
  Object.entries(currentFilters).forEach(([key, value]) => {
@@ -141,6 +170,5 @@ export function ExportDialog({ open, onOpenChange, model, metadata, currentFilte
141
170
  toast.error('Error al exportar los datos');
142
171
  }
143
172
  };
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..."] }))] })] }) }));
173
+ 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 ", 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.displayLabel })] }, 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
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "29.2.11",
3
+ "version": "29.2.12",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,16 @@
1
1
  // ExportDialog — lets users pick format (csv/json) + columns and kicks off
2
2
  // either a sync download or an async export job (polled via /exports/:id/status).
3
- // Axios-like client is provided by <ApiProvider>.
4
- import { useState, useEffect, useCallback } from 'react'
3
+ //
4
+ // Labels work for BOTH host styles:
5
+ // - Core DefineTable with human text ("Especialidades") → shown as-is via
6
+ // t(label, { defaultValue: label })
7
+ // - Manifest i18n keys ("purchases.field.state") → resolved by the host i18n
8
+ // bundle the same way DynamicTable already translates column headers
9
+ // The localized labels are also sent to the API as `column_labels` so the CSV
10
+ // headers match what the user sees in the dialog (backend may not have the
11
+ // addon locale bundles that only live on the frontend).
12
+ import { useState, useEffect, useCallback, useMemo } from 'react'
13
+ import { useTranslation } from 'react-i18next'
5
14
  import {
6
15
  Dialog,
7
16
  DialogContent,
@@ -40,6 +49,7 @@ export function ExportDialog({
40
49
  hasActiveFilters,
41
50
  }: ExportDialogProps) {
42
51
  const api = useApi()
52
+ const { t, i18n } = useTranslation()
43
53
  const [format, setFormat] = useState<'csv' | 'json'>('csv')
44
54
  const [exportAll, setExportAll] = useState(false)
45
55
  const [selectedColumns, setSelectedColumns] = useState<string[]>([])
@@ -48,12 +58,34 @@ export function ExportDialog({
48
58
  const [progress, setProgress] = useState(0)
49
59
  const [asyncJobId, setAsyncJobId] = useState<string | null>(null)
50
60
 
61
+ const tr = useCallback(
62
+ (label?: string, fallback?: string) => {
63
+ const raw = (label && label.trim()) || fallback || ''
64
+ if (!raw) return fallback || ''
65
+ return t(raw, { defaultValue: raw })
66
+ },
67
+ [t],
68
+ )
69
+
70
+ const title = useMemo(
71
+ () => tr((metadata as { titleKey?: string }).titleKey || metadata.title, metadata.title || model),
72
+ [metadata, model, tr],
73
+ )
74
+
75
+ const visibleColumns = useMemo(
76
+ () =>
77
+ (metadata?.columns?.filter((col) => !col.hidden) ?? []).map((col) => ({
78
+ ...col,
79
+ displayLabel: tr(col.label, col.key),
80
+ })),
81
+ [metadata, tr],
82
+ )
51
83
  useEffect(() => {
52
84
  if (open && metadata?.columns) {
53
85
  setSelectedColumns(
54
86
  metadata.columns
55
- .filter(col => !col.hidden)
56
- .map(col => col.key)
87
+ .filter((col) => !col.hidden)
88
+ .map((col) => col.key),
57
89
  )
58
90
  setFormat('csv')
59
91
  setExportAll(false)
@@ -68,21 +100,19 @@ export function ExportDialog({
68
100
  setSelectedColumns((prev: string[]) =>
69
101
  prev.includes(key)
70
102
  ? prev.filter((k: string) => k !== key)
71
- : [...prev, key]
103
+ : [...prev, key],
72
104
  )
73
105
  }, [])
74
106
 
75
107
  const toggleAllColumns = useCallback(() => {
76
- const visibleKeys = metadata.columns
77
- .filter(col => !col.hidden)
78
- .map(col => col.key)
108
+ const visibleKeys = visibleColumns.map((col) => col.key)
79
109
 
80
110
  if (selectedColumns.length === visibleKeys.length) {
81
111
  setSelectedColumns([])
82
112
  } else {
83
113
  setSelectedColumns(visibleKeys)
84
114
  }
85
- }, [metadata, selectedColumns])
115
+ }, [visibleColumns, selectedColumns])
86
116
 
87
117
  useEffect(() => {
88
118
  if (!asyncJobId) return
@@ -145,9 +175,20 @@ export function ExportDialog({
145
175
  setProgress(0)
146
176
 
147
177
  try {
178
+ const columnLabels: Record<string, string> = {}
179
+ for (const col of visibleColumns) {
180
+ if (selectedColumns.includes(col.key)) {
181
+ columnLabels[col.key] = col.displayLabel
182
+ }
183
+ }
184
+
148
185
  const params: Record<string, any> = {
149
186
  format,
150
187
  columns: selectedColumns.join(','),
188
+ // Localized headers so CSV matches the dialog (core text OR
189
+ // manifest i18n keys resolved by the host frontend).
190
+ column_labels: JSON.stringify(columnLabels),
191
+ lang: i18n.language || 'es',
151
192
  }
152
193
 
153
194
  if (!exportAll && currentFilters) {
@@ -190,13 +231,11 @@ export function ExportDialog({
190
231
  }
191
232
  }
192
233
 
193
- const visibleColumns = metadata?.columns?.filter(col => !col.hidden) ?? []
194
-
195
234
  return (
196
235
  <Dialog open={open} onOpenChange={onOpenChange}>
197
236
  <DialogContent className="sm:max-w-md max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden">
198
237
  <DialogHeader className="p-6 pb-4 border-b shrink-0">
199
- <DialogTitle>Exportar {metadata.title}</DialogTitle>
238
+ <DialogTitle>Exportar {title}</DialogTitle>
200
239
  <DialogDescription>
201
240
  Selecciona el formato y las columnas a exportar.
202
241
  </DialogDescription>
@@ -301,7 +340,7 @@ export function ExportDialog({
301
340
  htmlFor={`col-${col.key}`}
302
341
  className="font-normal cursor-pointer text-sm truncate"
303
342
  >
304
- {col.label}
343
+ {col.displayLabel}
305
344
  </Label>
306
345
  </div>
307
346
  ))}