@archbase/components 4.0.35 → 4.0.37

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.35",
3
+ "version": "4.0.37",
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.35",
122
- "@archbase/data": "4.0.35",
123
- "@archbase/layout": "4.0.35"
121
+ "@archbase/core": "4.0.37",
122
+ "@archbase/data": "4.0.37",
123
+ "@archbase/layout": "4.0.37"
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
  }
@@ -135,6 +135,8 @@ export interface ArchbaseAsyncSelectProps<T, ID, O> {
135
135
  converter?: (value: O) => any;
136
136
  /** Function que busca o valor original antes de converter pelo valor de retorno do converter */
137
137
  getConvertedOption?: (value: any) => Promise<O>;
138
+ /** Função que determina se uma opção individual está desabilitada */
139
+ isOptionDisabled?: (option: O) => boolean;
138
140
  }
139
141
  function buildOptions<O>(
140
142
  initialOptions: O[],
@@ -210,7 +212,8 @@ export function ArchbaseAsyncSelect<T, ID, O>({
210
212
  innerRef,
211
213
  onSearchChange,
212
214
  converter,
213
- getConvertedOption
215
+ getConvertedOption,
216
+ isOptionDisabled,
214
217
  }: ArchbaseAsyncSelectProps<T, ID, O>) {
215
218
  const forceUpdate = useForceUpdate();
216
219
 
@@ -539,7 +542,11 @@ export function ArchbaseAsyncSelect<T, ID, O>({
539
542
  {filteredOptions.slice(0, limit ? limit : filteredOptions.length).map((option) => {
540
543
  const {key, ...rest} = option
541
544
  return (
542
- <Combobox.Option value={option.value} key={option.key}>
545
+ <Combobox.Option
546
+ value={option.value}
547
+ key={option.key}
548
+ disabled={isOptionDisabled ? isOptionDisabled(option.origin) : false}
549
+ >
543
550
  {ItemComponent ? <ItemComponent {...rest} /> : option.label}
544
551
  </Combobox.Option>
545
552
  )
@@ -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',
@@ -117,6 +117,8 @@ export interface ArchbaseSelectProps<T, ID, O> {
117
117
  * Por exemplo: (id) => fetchObjectById(id) para converter ID de volta ao objeto
118
118
  */
119
119
  getConvertedOption?: (value: any) => Promise<O>
120
+ /** Função que determina se uma opção individual está desabilitada */
121
+ isOptionDisabled?: (option: O) => boolean
120
122
  }
121
123
 
122
124
  function buildGroupOptions(
@@ -248,7 +250,8 @@ export function ArchbaseSelect<T, ID, O>({
248
250
  classNames,
249
251
  styles,
250
252
  converter,
251
- getConvertedOption
253
+ getConvertedOption,
254
+ isOptionDisabled,
252
255
  }: ArchbaseSelectProps<T, ID, O>) {
253
256
  const forceUpdate = useForceUpdate();
254
257
 
@@ -282,7 +285,7 @@ export function ArchbaseSelect<T, ID, O>({
282
285
  const contextError = validationContext?.getError(fieldKey);
283
286
 
284
287
  const currentOptions: any[] = useMemo(() => {
285
- return buildOptions<O>(
288
+ const opts = buildOptions<O>(
286
289
  options,
287
290
  initialOptions,
288
291
  children,
@@ -290,6 +293,19 @@ export function ArchbaseSelect<T, ID, O>({
290
293
  getOptionValue,
291
294
  optionsLabelField
292
295
  )
296
+ if (!isOptionDisabled) return opts
297
+ return opts.map((opt: any) => {
298
+ if (opt.group !== undefined) {
299
+ return {
300
+ ...opt,
301
+ items: opt.items.map((item: any) => ({
302
+ ...item,
303
+ disabled: isOptionDisabled(item.origin ?? item),
304
+ })),
305
+ }
306
+ }
307
+ return { ...opt, disabled: isOptionDisabled(opt.origin ?? opt) }
308
+ })
293
309
  }, [
294
310
  updateCounter,
295
311
  options,
@@ -297,7 +313,8 @@ export function ArchbaseSelect<T, ID, O>({
297
313
  children,
298
314
  getOptionLabel,
299
315
  getOptionValue,
300
- optionsLabelField
316
+ optionsLabelField,
317
+ isOptionDisabled,
301
318
  ])
302
319
 
303
320
  const handleConverter = (value) => {
Binary file