@archbase/components 4.0.36 → 4.0.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archbase/components",
3
- "version": "4.0.36",
3
+ "version": "4.0.38",
4
4
  "description": "UI Components for Archbase React v3 - Form editors, data visualization, and business components",
5
5
  "author": "Edson Martins <edsonmartins2005@gmail.com>",
6
6
  "license": "MIT",
@@ -118,9 +118,9 @@
118
118
  "vis-timeline": "^7.7.3",
119
119
  "xlsx": "^0.18.5",
120
120
  "yet-another-react-lightbox": "^3.21.0",
121
- "@archbase/core": "4.0.36",
122
- "@archbase/data": "4.0.36",
123
- "@archbase/layout": "4.0.36"
121
+ "@archbase/core": "4.0.38",
122
+ "@archbase/data": "4.0.38",
123
+ "@archbase/layout": "4.0.38"
124
124
  },
125
125
  "devDependencies": {
126
126
  "@types/d3": "^7.4.3",
@@ -118,6 +118,12 @@ export interface ArchbaseDataGridAGColumnProps<T = any> {
118
118
  hideWhenNoPermission?: boolean;
119
119
  /** Auto-register column permission */
120
120
  autoRegisterPermission?: boolean;
121
+
122
+ /** Função para formatar o valor da célula para exportação (CSV/Excel). Útil quando render usa componentes React (ex: badges, arrays de objetos). */
123
+ exportValue?: (row: T) => string;
124
+
125
+ /** Se false, a coluna não aparece nas opções de exportação/impressão (ex: colunas de imagem). Padrão: true. */
126
+ exportable?: boolean;
121
127
  }
122
128
 
123
129
  /**
@@ -412,4 +418,6 @@ export function GridToolBarActions(props: GridToolBarActionsProps) {
412
418
  export interface ExtendedColDef<TData = any, TValue = any> extends ColDef<TData, TValue> {
413
419
  enableGlobalFilter?: boolean;
414
420
  dataType?: FieldDataType;
421
+ exportValue?: (row: TData) => string;
422
+ exportable?: boolean;
415
423
  }
@@ -541,6 +541,8 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
541
541
  // Custom properties
542
542
  enableGlobalFilter: columnProps.enableGlobalFilter,
543
543
  dataType: columnProps.dataType,
544
+ exportValue: columnProps.exportValue,
545
+ exportable: columnProps.exportable !== false,
544
546
  };
545
547
 
546
548
  cols.push(colDef);
@@ -1131,7 +1133,7 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
1131
1133
  // Modal columns for export/print
1132
1134
  const modalColumns = useMemo(() => {
1133
1135
  return columnDefs
1134
- .filter((col) => col.field && !col.field.startsWith('__'))
1136
+ .filter((col) => col.field && !col.field.startsWith('__') && col.exportable !== false)
1135
1137
  .map((col) => ({
1136
1138
  id: col.field!,
1137
1139
  title: col.headerName || col.field!,
@@ -358,4 +358,7 @@ export interface ArchbaseDataGridColumnProps<T = any> {
358
358
 
359
359
  /** Auto-registra a permissão da coluna (padrão: true) */
360
360
  autoRegisterPermission?: boolean;
361
+
362
+ /** Função para formatar o valor da célula para exportação (CSV/Excel). Útil quando render usa componentes React (ex: badges, arrays de objetos). */
363
+ exportValue?: (row: T) => string;
361
364
  }
@@ -1152,7 +1152,8 @@ function ArchbaseDataGrid<T extends object = any, ID = any>(props: ArchbaseDataG
1152
1152
  // Armazenar metadados personalizados
1153
1153
  columnMetadata[columnProps.dataField] = {
1154
1154
  enableGlobalFilter: columnProps.enableGlobalFilter,
1155
- dataType: columnProps.dataType
1155
+ dataType: columnProps.dataType,
1156
+ exportValue: columnProps.exportValue
1156
1157
  }
1157
1158
 
1158
1159
  // Obter o renderizador adequado para o tipo de dados
@@ -1246,6 +1247,7 @@ function ArchbaseDataGrid<T extends object = any, ID = any>(props: ArchbaseDataG
1246
1247
  if (metadata) {
1247
1248
  ;(col as any).enableGlobalFilter = metadata.enableGlobalFilter
1248
1249
  ;(col as any).dataType = metadata.dataType
1250
+ ;(col as any).exportValue = metadata.exportValue
1249
1251
  }
1250
1252
  })
1251
1253
 
@@ -2,6 +2,8 @@
2
2
  import { saveAs } from 'file-saver';
3
3
  import { utils, write } from 'xlsx';
4
4
  import { format } from 'date-fns';
5
+ import { jsPDF } from 'jspdf';
6
+ import autoTable from 'jspdf-autotable';
5
7
 
6
8
  // Tipos de exportação suportados
7
9
  export type ExportFormat = 'csv' | 'excel' | 'pdf';
@@ -37,6 +39,16 @@ const getCellValue = (row: any, column: any): any => {
37
39
  return value;
38
40
  }
39
41
 
42
+ // Usar exportValue se disponível (tem prioridade sobre valueGetter para exportação)
43
+ if (column.exportValue) {
44
+ try {
45
+ return column.exportValue(row);
46
+ } catch (e) {
47
+ console.error('Error in exportValue:', e);
48
+ return '';
49
+ }
50
+ }
51
+
40
52
  // Usar valueGetter se disponível
41
53
  if (column.valueGetter) {
42
54
  try {
@@ -176,8 +188,22 @@ async function exportToExcel(data: any[], config: ExportConfig): Promise<void> {
176
188
  saveAs(blob, `${config.filename || 'export'}.xlsx`);
177
189
  }
178
190
 
179
- // Exportação para PDF (placeholder - necessita implementação específica)
191
+ // Exportação para PDF
180
192
  async function exportToPDF(data: any[], config: ExportConfig): Promise<void> {
181
- console.warn('Exportação para PDF ainda não implementada. Considere usar a função printData em seu lugar.');
182
- // Implementar lógica de exportação PDF ou usar a função printData
193
+ if (!data || data.length === 0) return;
194
+
195
+ const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'A4' }) as any;
196
+
197
+ const headers = Object.keys(data[0]);
198
+ const tableData = data.map(row => headers.map(h => (row[h] !== null && row[h] !== undefined ? row[h] : '')));
199
+
200
+ autoTable(doc, {
201
+ head: [headers],
202
+ body: tableData,
203
+ theme: 'grid',
204
+ styles: { fontSize: 9, cellPadding: 2, font: 'helvetica', overflow: 'linebreak' },
205
+ headStyles: { fillColor: [51, 51, 51], textColor: 255, fontStyle: 'bold', halign: 'center' },
206
+ });
207
+
208
+ saveAs(doc.output('blob'), `${config.filename || 'export'}.pdf`);
183
209
  }
@@ -52,6 +52,10 @@ export interface ArchbaseAvatarEditProps<T, ID> {
52
52
  maxSizeKB?: number;
53
53
  /** Qualidade da compressão da imagem (0 a 1), sendo 1 melhor qualidade */
54
54
  imageQuality?: number;
55
+ /** Maior dimensão (px) da imagem final: o recorte é redimensionado automaticamente
56
+ * para caber neste limite antes da compressão, independente do tamanho da foto
57
+ * enviada pelo usuário. 0 mantém a resolução original do recorte. */
58
+ maxOutputSizePx?: number;
55
59
  }
56
60
 
57
61
  export function ArchbaseAvatarEdit<T, ID>({
@@ -75,6 +79,7 @@ export function ArchbaseAvatarEdit<T, ID>({
75
79
  hoverBackgroundColor = 'rgba(0, 0, 0, 0.6)',
76
80
  maxSizeKB = 0, // 0 significa sem limite
77
81
  imageQuality = 0.95,
82
+ maxOutputSizePx = 512, // avatar não precisa de resolução maior que isso
78
83
  ...otherProps
79
84
  }: ArchbaseAvatarEditProps<T, ID>) {
80
85
  const [value, setValue] = useState<string | undefined>(undefined);
@@ -342,6 +347,24 @@ export function ArchbaseAvatarEdit<T, ID>({
342
347
  // Colocar os dados da imagem rotacionada de volta no canvas
343
348
  ctx.putImageData(data, -pixelCrop.x, - pixelCrop.y);
344
349
 
350
+ // Redimensionar para a dimensão máxima de saída: reduzir a resolução (e não só a
351
+ // qualidade JPEG) garante arquivos pequenos mesmo para fotos de celular gigantes,
352
+ // sem o usuário precisar escolher uma "imagem menor"
353
+ const maiorDimensao = Math.max(pixelCrop.width, pixelCrop.height);
354
+ if (maxOutputSizePx > 0 && maiorDimensao > maxOutputSizePx) {
355
+ const escala = maxOutputSizePx / maiorDimensao;
356
+ const canvasSaida = document.createElement('canvas');
357
+ canvasSaida.width = Math.max(1, Math.round(pixelCrop.width * escala));
358
+ canvasSaida.height = Math.max(1, Math.round(pixelCrop.height * escala));
359
+ const ctxSaida = canvasSaida.getContext('2d');
360
+ if (ctxSaida) {
361
+ ctxSaida.imageSmoothingEnabled = true;
362
+ ctxSaida.imageSmoothingQuality = 'high';
363
+ ctxSaida.drawImage(canvas, 0, 0, canvasSaida.width, canvasSaida.height);
364
+ return canvasSaida.toDataURL('image/jpeg', quality);
365
+ }
366
+ }
367
+
345
368
  // Retornar como base64 com a qualidade especificada
346
369
  return canvas.toDataURL('image/jpeg', quality);
347
370
  }
@@ -448,6 +448,48 @@ export function ArchbaseNumberEdit<T, ID>({
448
448
  }
449
449
  };
450
450
 
451
+ const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
452
+ event.preventDefault();
453
+
454
+ let pastedText = event.clipboardData.getData('text').trim();
455
+
456
+ if (!allowNegative) {
457
+ pastedText = pastedText.replace(/-/g, '');
458
+ }
459
+
460
+ // Normaliza separador decimal: aceita tanto '.' quanto ',' como entrada
461
+ const otherSeparator = decimalSeparator === '.' ? ',' : '.';
462
+ pastedText = pastedText
463
+ .replace(new RegExp(`\\${thousandSeparator}`, 'g'), '')
464
+ .replace(otherSeparator, decimalSeparator);
465
+
466
+ const { maskedValue: newMaskedValue, value: newValue } = formatNumber(
467
+ pastedText,
468
+ precision,
469
+ decimalSeparator,
470
+ thousandSeparator,
471
+ allowNegative,
472
+ prefix,
473
+ suffix,
474
+ minValue,
475
+ maxValue,
476
+ false
477
+ );
478
+
479
+ if (newMaskedValue === null && newValue === null) return;
480
+
481
+ setMaskedValue(newMaskedValue);
482
+ setCurrentValue(newValue);
483
+
484
+ if (dataSource && !dataSource.isBrowsing() && dataField) {
485
+ v1v2Compatibility.handleValueChange(newValue);
486
+ }
487
+
488
+ if (onChangeValue) {
489
+ onChangeValue(newMaskedValue, newValue, event as any);
490
+ }
491
+ };
492
+
451
493
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
452
494
  const input = event.currentTarget;
453
495
  const selectionStart = input.selectionStart || 0;
@@ -601,6 +643,7 @@ export function ArchbaseNumberEdit<T, ID>({
601
643
  onBlur={handleFocusExit}
602
644
  onFocus={handleFocusEnter}
603
645
  onKeyDown={handleKeyDown}
646
+ onPaste={handlePaste}
604
647
  styles={{
605
648
  input: {
606
649
  textAlign: 'right',
@@ -17,6 +17,21 @@ let SunEditorComponent: React.ComponentType<any> | null = null;
17
17
  let langModules: { en: any; es: any; ptBR: any } = { en: {}, es: {}, ptBR: {} };
18
18
  let loadPromise: Promise<void> | null = null;
19
19
 
20
+ /**
21
+ * Resolve o componente de um módulo importado dinamicamente lidando com o
22
+ * interop CJS→ESM. `suneditor-react` é publicado como CJS (`exports.default = SunEditor`
23
+ * com `__esModule: true`); dependendo do empacotador (ex.: build de produção com Rollup),
24
+ * `mod.default` pode chegar como o objeto `{ default: SunEditor, __esModule: true }` em vez
25
+ * do próprio componente, fazendo o React lançar "Element type is invalid ... got: object".
26
+ */
27
+ function resolveDefaultComponent(mod: any): any {
28
+ const candidate = mod?.default ?? mod;
29
+ if (candidate && typeof candidate !== 'function' && candidate.__esModule && candidate.default) {
30
+ return candidate.default;
31
+ }
32
+ return candidate;
33
+ }
34
+
20
35
  // Função para carregar suneditor e dependências
21
36
  async function loadSunEditor(): Promise<void> {
22
37
  if (loadPromise) return loadPromise;
@@ -31,7 +46,7 @@ async function loadSunEditor(): Promise<void> {
31
46
  import('suneditor/src/lang/es'),
32
47
  import('suneditor/src/lang/pt_br'),
33
48
  ]);
34
- SunEditorComponent = sunEditorModule.default;
49
+ SunEditorComponent = resolveDefaultComponent(sunEditorModule);
35
50
  langModules = {
36
51
  en: enModule.default,
37
52
  es: esModule.default,
Binary file