@archbase/components 3.0.25 → 4.0.1
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/dist/buttons/ArchbaseSpeedDial.d.ts +59 -0
- package/dist/buttons/index.d.ts +2 -0
- package/dist/charts/ArchbaseArcGauge.d.ts +55 -0
- package/dist/charts/ArchbaseGantt.d.ts +70 -0
- package/dist/charts/ArchbaseLinearGauge.d.ts +55 -0
- package/dist/charts/ArchbaseResourceTimeline.d.ts +61 -0
- package/dist/charts/ArchbaseSparkline.d.ts +45 -0
- package/dist/charts/index.d.ts +10 -0
- package/dist/containers/ArchbaseExpansionPanel.d.ts +60 -0
- package/dist/containers/ArchbaseFloatingPanel.d.ts +91 -0
- package/dist/containers/index.d.ts +4 -0
- package/dist/datagrid/export/ArchbaseDataGridExcelExport.d.ts +67 -0
- package/dist/datagrid/export/index.d.ts +2 -0
- package/dist/datagrid/index.d.ts +1 -0
- package/dist/display/ArchbaseAnimationWrapper.d.ts +73 -0
- package/dist/display/ArchbaseBarcodeGenerator.d.ts +50 -0
- package/dist/display/ArchbaseCarousel.d.ts +100 -0
- package/dist/display/ArchbaseCodeViewer.d.ts +63 -0
- package/dist/display/ArchbaseDiffViewer.d.ts +41 -0
- package/dist/display/ArchbaseJsonTree.d.ts +34 -0
- package/dist/display/ArchbaseLightbox.d.ts +89 -0
- package/dist/display/ArchbaseMarquee.d.ts +46 -0
- package/dist/display/ArchbasePhotoAlbum.d.ts +58 -0
- package/dist/display/ArchbaseQRCode.d.ts +44 -0
- package/dist/display/ArchbaseTooltipRich.d.ts +82 -0
- package/dist/display/ArchbaseVirtualList.d.ts +116 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/editors/ArchbaseAIPromptInput.d.ts +69 -0
- package/dist/editors/ArchbaseAsyncMultiSelect.d.ts +1 -1
- package/dist/editors/ArchbaseAsyncSelect.d.ts +1 -1
- package/dist/editors/ArchbaseBarcodeScanner.d.ts +63 -0
- package/dist/editors/ArchbaseCepInput.d.ts +26 -0
- package/dist/editors/ArchbaseColorGradientPicker.d.ts +80 -0
- package/dist/editors/ArchbaseCpfCnpjInput.d.ts +21 -0
- package/dist/editors/ArchbaseCurrencyInput.d.ts +25 -0
- package/dist/editors/ArchbaseDualListbox.d.ts +61 -0
- package/dist/editors/ArchbaseInPlaceEditor.d.ts +67 -0
- package/dist/editors/ArchbaseLightGrid.d.ts +56 -0
- package/dist/editors/ArchbaseMentionInput.d.ts +75 -0
- package/dist/editors/ArchbaseMultiEmail.d.ts +53 -0
- package/dist/editors/ArchbaseMultiViewCalendar.d.ts +47 -0
- package/dist/editors/ArchbaseNumberStepper.d.ts +66 -0
- package/dist/editors/ArchbaseOTPInput.d.ts +27 -0
- package/dist/editors/ArchbasePhoneInput.d.ts +52 -0
- package/dist/editors/ArchbaseRangeSlider.d.ts +22 -0
- package/dist/editors/ArchbaseSignaturePad.d.ts +60 -0
- package/dist/editors/ArchbaseSpeechToTextInput.d.ts +106 -0
- package/dist/editors/ArchbaseSpreadsheet.d.ts +56 -0
- package/dist/editors/ArchbaseTagInput.d.ts +55 -0
- package/dist/editors/index.d.ts +40 -0
- package/dist/feedback/ArchbaseChunkProgressBar.d.ts +35 -0
- package/dist/feedback/ArchbaseRipple.d.ts +43 -0
- package/dist/feedback/ArchbaseSkeleton.d.ts +115 -0
- package/dist/feedback/index.d.ts +6 -0
- package/dist/filters/ArchbaseColumnSelector.d.ts +38 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/forms/ArchbaseFieldArray.d.ts +80 -0
- package/dist/forms/ArchbaseFormWizard.d.ts +73 -0
- package/dist/forms/ArchbaseStepper.d.ts +51 -0
- package/dist/forms/index.d.ts +8 -0
- package/dist/forms/useArchbaseFormWizard.d.ts +74 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useArchbaseConfirm.d.ts +59 -0
- package/dist/image/editor/index.d.ts +43 -4
- package/dist/image/index.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +22510 -13820
- package/dist/list/ArchbaseTreeList.d.ts +76 -0
- package/dist/list/index.d.ts +2 -0
- package/dist/navigation/ArchbaseActionSheet.d.ts +70 -0
- package/dist/navigation/ArchbaseAppBar.d.ts +63 -0
- package/dist/navigation/ArchbaseBottomNavigation.d.ts +61 -0
- package/dist/navigation/ArchbaseContextMenu.d.ts +76 -0
- package/dist/navigation/ArchbaseKeyboardShortcuts.d.ts +85 -0
- package/dist/navigation/ArchbaseSpotlight.d.ts +69 -0
- package/dist/navigation/index.d.ts +12 -0
- package/dist/notification/ArchbaseNotificationCenter.d.ts +56 -0
- package/dist/notification/index.d.ts +2 -0
- package/dist/onboarding/ArchbaseOnboardingTour.d.ts +27 -76
- package/dist/viewers/ArchbaseDocViewer.d.ts +40 -0
- package/dist/viewers/ArchbasePdfBuilder.d.ts +53 -0
- package/dist/viewers/ArchbaseXmlViewer.d.ts +36 -0
- package/dist/viewers/index.d.ts +6 -0
- package/package.json +41 -18
- package/src/buttons/ArchbaseSpeedDial.tsx +282 -0
- package/src/buttons/index.ts +3 -0
- package/src/charts/ArchbaseArcGauge.tsx +325 -0
- package/src/charts/ArchbaseGantt.tsx +494 -0
- package/src/charts/ArchbaseLinearGauge.tsx +288 -0
- package/src/charts/ArchbaseResourceTimeline.tsx +433 -0
- package/src/charts/ArchbaseSparkline.tsx +193 -0
- package/src/charts/index.ts +20 -1
- package/src/containers/ArchbaseExpansionPanel.tsx +208 -0
- package/src/containers/ArchbaseFloatingPanel.tsx +448 -0
- package/src/containers/index.ts +17 -0
- package/src/datagrid/components/pagination/grid-pagination.tsx +2 -2
- package/src/datagrid/export/ArchbaseDataGridExcelExport.tsx +293 -0
- package/src/datagrid/export/index.ts +8 -0
- package/src/datagrid/index.tsx +2 -0
- package/src/display/ArchbaseAnimationWrapper.tsx +272 -0
- package/src/display/ArchbaseBarcodeGenerator.tsx +303 -0
- package/src/display/ArchbaseCarousel.tsx +679 -0
- package/src/display/ArchbaseCodeViewer.tsx +271 -0
- package/src/display/ArchbaseDiffViewer.tsx +108 -0
- package/src/display/ArchbaseJsonTree.tsx +150 -0
- package/src/display/ArchbaseLightbox.tsx +279 -0
- package/src/display/ArchbaseMarquee.tsx +268 -0
- package/src/display/ArchbasePhotoAlbum.tsx +415 -0
- package/src/display/ArchbaseQRCode.tsx +137 -0
- package/src/display/ArchbaseTooltipRich.tsx +317 -0
- package/src/display/ArchbaseVirtualList.tsx +618 -0
- package/src/display/index.ts +94 -1
- package/src/editors/ArchbaseAIPromptInput.tsx +432 -0
- package/src/editors/ArchbaseBarcodeScanner.tsx +570 -0
- package/src/editors/ArchbaseCepInput.tsx +173 -0
- package/src/editors/ArchbaseColorGradientPicker.tsx +460 -0
- package/src/editors/ArchbaseCpfCnpjInput.tsx +193 -0
- package/src/editors/ArchbaseCurrencyInput.tsx +100 -0
- package/src/editors/ArchbaseDualListbox.tsx +671 -0
- package/src/editors/ArchbaseInPlaceEditor.tsx +406 -0
- package/src/editors/ArchbaseLightGrid.tsx +538 -0
- package/src/editors/ArchbaseMentionInput.tsx +384 -0
- package/src/editors/ArchbaseMultiEmail.tsx +367 -0
- package/src/editors/ArchbaseMultiViewCalendar.tsx +439 -0
- package/src/editors/ArchbaseNumberStepper.tsx +219 -0
- package/src/editors/ArchbaseOTPInput.tsx +133 -0
- package/src/editors/ArchbaseOperationHoursEditor.tsx +1 -1
- package/src/editors/ArchbasePhoneInput.tsx +355 -0
- package/src/editors/ArchbaseRangeSlider.tsx +109 -0
- package/src/editors/ArchbaseSignaturePad.tsx +425 -0
- package/src/editors/ArchbaseSpeechToTextInput.tsx +444 -0
- package/src/editors/ArchbaseSpreadsheet.tsx +388 -0
- package/src/editors/ArchbaseTagInput.tsx +326 -0
- package/src/editors/index.tsx +92 -0
- package/src/feedback/ArchbaseChunkProgressBar.tsx +156 -0
- package/src/feedback/ArchbaseRipple.tsx +278 -0
- package/src/feedback/ArchbaseSkeleton.tsx +419 -0
- package/src/feedback/index.ts +22 -1
- package/src/filters/ArchbaseColumnSelector.tsx +268 -0
- package/src/filters/index.ts +2 -0
- package/src/forms/ArchbaseFieldArray.tsx +305 -0
- package/src/forms/ArchbaseFormWizard.tsx +284 -0
- package/src/forms/ArchbaseStepper.tsx +137 -0
- package/src/forms/index.ts +24 -2
- package/src/forms/useArchbaseFormWizard.ts +283 -0
- package/src/hooks/index.ts +12 -0
- package/src/hooks/useArchbaseConfirm.ts +293 -0
- package/src/image/editor/functions/image-processing.ts +5 -0
- package/src/image/editor/index.tsx +305 -391
- package/src/image/index.ts +1 -0
- package/src/index.ts +3 -0
- package/src/list/ArchbaseTreeList.tsx +422 -0
- package/src/list/index.ts +3 -0
- package/src/navigation/ArchbaseActionSheet.tsx +301 -0
- package/src/navigation/ArchbaseAppBar.tsx +233 -0
- package/src/navigation/ArchbaseBottomNavigation.tsx +243 -0
- package/src/navigation/ArchbaseContextMenu.tsx +298 -0
- package/src/navigation/ArchbaseKeyboardShortcuts.tsx +472 -0
- package/src/navigation/ArchbaseSpotlight.tsx +483 -0
- package/src/navigation/index.ts +48 -1
- package/src/notification/ArchbaseNotificationCenter.tsx +502 -0
- package/src/notification/index.ts +2 -0
- package/src/onboarding/ArchbaseOnboardingTour.tsx +229 -102
- package/src/viewers/ArchbaseDocViewer.tsx +584 -0
- package/src/viewers/ArchbasePdfBuilder.tsx +291 -0
- package/src/viewers/ArchbaseXmlViewer.tsx +142 -0
- package/src/viewers/index.ts +12 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchbaseSpreadsheet — wrapper para fortune-sheet integrado ao DataSource.
|
|
3
|
+
* Permite exibir e editar dados tabulares em formato de planilha.
|
|
4
|
+
* @status experimental
|
|
5
|
+
*/
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
+
import type { CSSProperties } from 'react';
|
|
8
|
+
import { Box, Input } from '@mantine/core';
|
|
9
|
+
import { useForceUpdate } from '@mantine/hooks';
|
|
10
|
+
import { Workbook } from '@fortune-sheet/react';
|
|
11
|
+
import '@fortune-sheet/react/dist/index.css';
|
|
12
|
+
import type { DataSourceEvent, IArchbaseDataSourceBase } from '@archbase/data';
|
|
13
|
+
import { DataSourceEventNames, useArchbaseDataSourceListener } from '@archbase/data';
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────── Types ───────────────────────────
|
|
16
|
+
|
|
17
|
+
export type ArchbaseColumnType = 'text' | 'number' | 'date' | 'boolean' | 'currency';
|
|
18
|
+
|
|
19
|
+
export interface ArchbaseColumnDef<T> {
|
|
20
|
+
/** Campo do registro mapeado para esta coluna */
|
|
21
|
+
field: keyof T | string;
|
|
22
|
+
/** Cabeçalho exibido na planilha */
|
|
23
|
+
header: string;
|
|
24
|
+
/** Largura da coluna em pixels */
|
|
25
|
+
width?: number;
|
|
26
|
+
/** Tipo do dado */
|
|
27
|
+
type?: ArchbaseColumnType;
|
|
28
|
+
/** Formato numérico (ex: '#,##0.00') */
|
|
29
|
+
format?: string;
|
|
30
|
+
/** Se a coluna é editável */
|
|
31
|
+
editable?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ArchbaseSpreadsheetProps<T, ID> {
|
|
35
|
+
/** Fonte de dados que fornece os registros */
|
|
36
|
+
dataSource?: IArchbaseDataSourceBase<T>;
|
|
37
|
+
/** Definição de colunas para mapeamento DataSource -> planilha */
|
|
38
|
+
columns?: ArchbaseColumnDef<T>[];
|
|
39
|
+
/** Dados standalone no formato Sheet[] do fortune-sheet */
|
|
40
|
+
data?: any[];
|
|
41
|
+
/** Permite edição (default: true) */
|
|
42
|
+
editable?: boolean;
|
|
43
|
+
/** Exibe a toolbar (default: true) */
|
|
44
|
+
showToolbar?: boolean;
|
|
45
|
+
/** Exibe a barra de fórmulas (default: true) */
|
|
46
|
+
showFormulaBar?: boolean;
|
|
47
|
+
/** Exibe as abas de planilhas (default: true) */
|
|
48
|
+
showSheetTabs?: boolean;
|
|
49
|
+
/** Altura do componente */
|
|
50
|
+
height?: string | number;
|
|
51
|
+
/** Largura do componente */
|
|
52
|
+
width?: string | number;
|
|
53
|
+
/** Callback quando uma célula é alterada */
|
|
54
|
+
onCellChange?: (row: number, col: number, oldValue: any, newValue: any) => void;
|
|
55
|
+
/** Callback quando a seleção muda */
|
|
56
|
+
onSelectionChange?: (ranges: any[]) => void;
|
|
57
|
+
/** Rótulo (Input.Wrapper) */
|
|
58
|
+
label?: string;
|
|
59
|
+
/** Descrição (Input.Wrapper) */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Somente leitura */
|
|
62
|
+
readOnly?: boolean;
|
|
63
|
+
/** Locale (default: 'pt-BR') */
|
|
64
|
+
locale?: string;
|
|
65
|
+
/** Estilo adicional do container */
|
|
66
|
+
style?: CSSProperties;
|
|
67
|
+
/** Classe CSS adicional */
|
|
68
|
+
className?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─────────────────────── Helpers ─────────────────────────
|
|
72
|
+
|
|
73
|
+
/** Retorna o valor de um campo possivelmente aninhado (notacao de ponto) */
|
|
74
|
+
function getNestedValue(obj: any, path: string): any {
|
|
75
|
+
if (!obj || !path) return undefined;
|
|
76
|
+
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Mapeia ArchbaseColumnType para o cell type do fortune-sheet */
|
|
80
|
+
function mapColumnTypeToCellType(type?: ArchbaseColumnType): number {
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'number':
|
|
83
|
+
case 'currency':
|
|
84
|
+
return 0; // number
|
|
85
|
+
case 'boolean':
|
|
86
|
+
return 0; // tratado como number (0/1)
|
|
87
|
+
case 'date':
|
|
88
|
+
return 0; // date stored as number in sheets
|
|
89
|
+
case 'text':
|
|
90
|
+
default:
|
|
91
|
+
return 1; // string
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Converte valor do DataSource para valor da célula */
|
|
96
|
+
function toCellValue(value: any, type?: ArchbaseColumnType): any {
|
|
97
|
+
if (value === null || value === undefined) return '';
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'boolean':
|
|
100
|
+
return value ? 1 : 0;
|
|
101
|
+
case 'number':
|
|
102
|
+
case 'currency':
|
|
103
|
+
return typeof value === 'number' ? value : Number(value) || 0;
|
|
104
|
+
default:
|
|
105
|
+
return String(value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Converte valor da célula de volta para o tipo esperado pelo DataSource */
|
|
110
|
+
function fromCellValue(value: any, type?: ArchbaseColumnType): any {
|
|
111
|
+
if (value === '' || value === null || value === undefined) return null;
|
|
112
|
+
switch (type) {
|
|
113
|
+
case 'boolean':
|
|
114
|
+
return Boolean(Number(value));
|
|
115
|
+
case 'number':
|
|
116
|
+
case 'currency':
|
|
117
|
+
return Number(value);
|
|
118
|
+
default:
|
|
119
|
+
return String(value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─────────────────────── Conversao DS -> Sheet ───────────────────────
|
|
124
|
+
|
|
125
|
+
function buildSheetFromDataSource<T>(
|
|
126
|
+
dataSource: IArchbaseDataSourceBase<T>,
|
|
127
|
+
columns: ArchbaseColumnDef<T>[],
|
|
128
|
+
): any {
|
|
129
|
+
const records = dataSource.browseRecords();
|
|
130
|
+
const celldata: any[] = [];
|
|
131
|
+
|
|
132
|
+
// Header row
|
|
133
|
+
columns.forEach((col, colIdx) => {
|
|
134
|
+
celldata.push({
|
|
135
|
+
r: 0,
|
|
136
|
+
c: colIdx,
|
|
137
|
+
v: {
|
|
138
|
+
v: col.header,
|
|
139
|
+
ct: { fa: 'General', t: 's' },
|
|
140
|
+
m: col.header,
|
|
141
|
+
bl: 1, // bold
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Data rows
|
|
147
|
+
records.forEach((record, rowIdx) => {
|
|
148
|
+
columns.forEach((col, colIdx) => {
|
|
149
|
+
const raw = getNestedValue(record, String(col.field));
|
|
150
|
+
const value = toCellValue(raw, col.type);
|
|
151
|
+
const ct =
|
|
152
|
+
col.type === 'number' || col.type === 'currency'
|
|
153
|
+
? { fa: col.format || 'General', t: 'n' }
|
|
154
|
+
: { fa: 'General', t: 's' };
|
|
155
|
+
|
|
156
|
+
celldata.push({
|
|
157
|
+
r: rowIdx + 1, // +1 for header
|
|
158
|
+
c: colIdx,
|
|
159
|
+
v: {
|
|
160
|
+
v: value,
|
|
161
|
+
ct,
|
|
162
|
+
m: String(value),
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Column widths
|
|
169
|
+
const columnlen: Record<number, number> = {};
|
|
170
|
+
columns.forEach((col, idx) => {
|
|
171
|
+
columnlen[idx] = col.width ?? 100;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
name: 'Sheet1',
|
|
176
|
+
index: '0',
|
|
177
|
+
status: 1,
|
|
178
|
+
order: 0,
|
|
179
|
+
celldata,
|
|
180
|
+
column: columns.length,
|
|
181
|
+
row: records.length + 1,
|
|
182
|
+
config: {
|
|
183
|
+
columnlen,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─────────────────────── Componente ─────────────────────────
|
|
189
|
+
|
|
190
|
+
function ArchbaseSpreadsheetInner<T, ID>(
|
|
191
|
+
props: ArchbaseSpreadsheetProps<T, ID>,
|
|
192
|
+
) {
|
|
193
|
+
const {
|
|
194
|
+
dataSource,
|
|
195
|
+
columns,
|
|
196
|
+
data: externalData,
|
|
197
|
+
editable = true,
|
|
198
|
+
showToolbar = true,
|
|
199
|
+
showFormulaBar = true,
|
|
200
|
+
showSheetTabs = true,
|
|
201
|
+
height = '400px',
|
|
202
|
+
width = '100%',
|
|
203
|
+
onCellChange,
|
|
204
|
+
onSelectionChange,
|
|
205
|
+
label,
|
|
206
|
+
description,
|
|
207
|
+
readOnly = false,
|
|
208
|
+
locale = 'pt-BR',
|
|
209
|
+
style,
|
|
210
|
+
className,
|
|
211
|
+
} = props;
|
|
212
|
+
|
|
213
|
+
const forceUpdate = useForceUpdate();
|
|
214
|
+
const prevCellDataRef = useRef<Map<string, any>>(new Map());
|
|
215
|
+
|
|
216
|
+
// ── Construir dados da planilha ──
|
|
217
|
+
|
|
218
|
+
const sheetData = useMemo(() => {
|
|
219
|
+
if (externalData && externalData.length > 0) {
|
|
220
|
+
return externalData;
|
|
221
|
+
}
|
|
222
|
+
if (dataSource && columns && columns.length > 0) {
|
|
223
|
+
return [buildSheetFromDataSource(dataSource, columns)];
|
|
224
|
+
}
|
|
225
|
+
return [
|
|
226
|
+
{
|
|
227
|
+
name: 'Sheet1',
|
|
228
|
+
index: '0',
|
|
229
|
+
status: 1,
|
|
230
|
+
order: 0,
|
|
231
|
+
celldata: [],
|
|
232
|
+
column: 10,
|
|
233
|
+
row: 30,
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
}, [externalData, dataSource, columns]);
|
|
237
|
+
|
|
238
|
+
// ── Guardar referencia a valores anteriores para detectar mudancas ──
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const map = new Map<string, any>();
|
|
242
|
+
if (sheetData && sheetData.length > 0) {
|
|
243
|
+
const sheet = sheetData[0];
|
|
244
|
+
if (sheet.celldata) {
|
|
245
|
+
for (const cell of sheet.celldata) {
|
|
246
|
+
map.set(`${cell.r}_${cell.c}`, cell.v?.v);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
prevCellDataRef.current = map;
|
|
251
|
+
}, [sheetData]);
|
|
252
|
+
|
|
253
|
+
// ── Listener do DataSource ──
|
|
254
|
+
|
|
255
|
+
const handleDataSourceEvent = useCallback(
|
|
256
|
+
(event: DataSourceEvent<T>) => {
|
|
257
|
+
switch (event.type) {
|
|
258
|
+
case DataSourceEventNames.dataChanged:
|
|
259
|
+
case DataSourceEventNames.recordChanged:
|
|
260
|
+
case DataSourceEventNames.refreshData:
|
|
261
|
+
case DataSourceEventNames.afterInsert:
|
|
262
|
+
case DataSourceEventNames.afterRemove:
|
|
263
|
+
case DataSourceEventNames.afterCancel:
|
|
264
|
+
case DataSourceEventNames.afterSave:
|
|
265
|
+
case DataSourceEventNames.afterEdit:
|
|
266
|
+
forceUpdate();
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[forceUpdate],
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (dataSource) {
|
|
274
|
+
useArchbaseDataSourceListener<T>({
|
|
275
|
+
dataSource,
|
|
276
|
+
listener: handleDataSourceEvent,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── Handlers fortune-sheet ──
|
|
281
|
+
|
|
282
|
+
const handleCellUpdated = useCallback(
|
|
283
|
+
(row: number, col: number, newValue: any) => {
|
|
284
|
+
const key = `${row}_${col}`;
|
|
285
|
+
const oldValue = prevCellDataRef.current.get(key);
|
|
286
|
+
prevCellDataRef.current.set(key, newValue);
|
|
287
|
+
|
|
288
|
+
// Callback externo
|
|
289
|
+
onCellChange?.(row, col, oldValue, newValue);
|
|
290
|
+
|
|
291
|
+
// Atualizar DataSource se em modo de edicao
|
|
292
|
+
if (dataSource && columns && columns.length > 0 && row > 0) {
|
|
293
|
+
const recordIndex = row - 1; // header na linha 0
|
|
294
|
+
const colDef = columns[col];
|
|
295
|
+
if (colDef && colDef.editable !== false) {
|
|
296
|
+
const records = dataSource.browseRecords();
|
|
297
|
+
if (recordIndex >= 0 && recordIndex < records.length) {
|
|
298
|
+
// Navegar para o registro correspondente
|
|
299
|
+
if (dataSource.getCurrentIndex() !== recordIndex) {
|
|
300
|
+
dataSource.gotoRecord(recordIndex);
|
|
301
|
+
}
|
|
302
|
+
if (dataSource.isEditing() || dataSource.isInserting()) {
|
|
303
|
+
const converted = fromCellValue(newValue, colDef.type);
|
|
304
|
+
dataSource.setFieldValue(String(colDef.field), converted);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
[dataSource, columns, onCellChange],
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// ── Fortune-sheet onChange handler ──
|
|
314
|
+
|
|
315
|
+
const handleChange = useCallback(
|
|
316
|
+
(data: any[]) => {
|
|
317
|
+
// fortune-sheet dispara onChange com os dados atualizados
|
|
318
|
+
// Detectar mudancas de celula comparando com o estado anterior
|
|
319
|
+
if (data && data.length > 0) {
|
|
320
|
+
const sheet = data[0];
|
|
321
|
+
if (sheet?.celldata) {
|
|
322
|
+
for (const cell of sheet.celldata) {
|
|
323
|
+
const key = `${cell.r}_${cell.c}`;
|
|
324
|
+
const prev = prevCellDataRef.current.get(key);
|
|
325
|
+
const cur = cell.v?.v;
|
|
326
|
+
if (prev !== cur) {
|
|
327
|
+
handleCellUpdated(cell.r, cell.c, cur);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
[handleCellUpdated],
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// ── Locale mapping ──
|
|
337
|
+
|
|
338
|
+
const lang = useMemo(() => {
|
|
339
|
+
if (locale?.startsWith('pt')) return 'pt-BR' as const;
|
|
340
|
+
if (locale?.startsWith('es')) return 'es' as const;
|
|
341
|
+
if (locale?.startsWith('zh')) return 'zh' as const;
|
|
342
|
+
return 'en' as const;
|
|
343
|
+
}, [locale]);
|
|
344
|
+
|
|
345
|
+
// ── Computed height ──
|
|
346
|
+
|
|
347
|
+
const computedHeight = typeof height === 'number' ? `${height}px` : height;
|
|
348
|
+
const computedWidth = typeof width === 'number' ? `${width}px` : width;
|
|
349
|
+
|
|
350
|
+
// ── Render ──
|
|
351
|
+
|
|
352
|
+
const spreadsheetContent = (
|
|
353
|
+
<Box
|
|
354
|
+
className={className}
|
|
355
|
+
style={{
|
|
356
|
+
height: computedHeight,
|
|
357
|
+
width: computedWidth,
|
|
358
|
+
position: 'relative',
|
|
359
|
+
overflow: 'hidden',
|
|
360
|
+
...style,
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
<Workbook
|
|
364
|
+
data={sheetData}
|
|
365
|
+
onChange={handleChange}
|
|
366
|
+
allowEdit={editable && !readOnly}
|
|
367
|
+
showToolbar={showToolbar}
|
|
368
|
+
showFormulaBar={showFormulaBar}
|
|
369
|
+
showSheetTabs={showSheetTabs}
|
|
370
|
+
lang={lang}
|
|
371
|
+
/>
|
|
372
|
+
</Box>
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (label || description) {
|
|
376
|
+
return (
|
|
377
|
+
<Input.Wrapper label={label} description={description}>
|
|
378
|
+
{spreadsheetContent}
|
|
379
|
+
</Input.Wrapper>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return spreadsheetContent;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export const ArchbaseSpreadsheet = React.memo(ArchbaseSpreadsheetInner) as typeof ArchbaseSpreadsheetInner;
|
|
387
|
+
|
|
388
|
+
(ArchbaseSpreadsheet as any).displayName = 'ArchbaseSpreadsheet';
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchbaseTagInput — editor de tags integrado ao DataSource (v1/v2) com suporte a validacao e estado controlado.
|
|
3
|
+
* Utiliza o componente TagsInput do Mantine internamente.
|
|
4
|
+
* @status stable
|
|
5
|
+
*/
|
|
6
|
+
import { MantineSize, TagsInput } from '@mantine/core';
|
|
7
|
+
import { useForceUpdate } from '@mantine/hooks';
|
|
8
|
+
import type { CSSProperties, FocusEventHandler } from 'react';
|
|
9
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import type { DataSourceEvent, IArchbaseDataSourceBase } from '@archbase/data';
|
|
11
|
+
import { DataSourceEventNames, useArchbaseDidUpdate, useArchbaseV1V2Compatibility } from '@archbase/data';
|
|
12
|
+
import { useValidationErrors } from '@archbase/core';
|
|
13
|
+
|
|
14
|
+
export interface ArchbaseTagInputProps<T, ID> {
|
|
15
|
+
/** Fonte de dados onde sera atribuido o valor das tags (V1 ou V2) */
|
|
16
|
+
dataSource?: IArchbaseDataSourceBase<T>;
|
|
17
|
+
/** Campo onde devera ser atribuido o valor das tags na fonte de dados */
|
|
18
|
+
dataField?: string;
|
|
19
|
+
/** Valor controlado (array de strings) quando usado sem dataSource */
|
|
20
|
+
value?: string[];
|
|
21
|
+
/** Valor padrao inicial (array de strings) */
|
|
22
|
+
defaultValue?: string[];
|
|
23
|
+
/** Delimitador usado para serializar/deserializar quando o campo no dataSource e uma string. Padrao: ',' */
|
|
24
|
+
delimiter?: string;
|
|
25
|
+
/** Titulo do campo */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Descricao do campo */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Ultimo erro ocorrido no campo */
|
|
30
|
+
error?: string;
|
|
31
|
+
/** Texto sugestao do campo */
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
/** Indicador se o preenchimento e obrigatorio */
|
|
34
|
+
required?: boolean;
|
|
35
|
+
/** Indicador se o campo esta desabilitado */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Indicador se o campo e somente leitura. Obs: usado em conjunto com o status da fonte de dados */
|
|
38
|
+
readOnly?: boolean;
|
|
39
|
+
/** Lista de caracteres que, ao serem digitados, criam uma nova tag. Ex: [',', ' ', 'Enter'] */
|
|
40
|
+
separators?: string[];
|
|
41
|
+
/** Numero maximo de tags permitidas */
|
|
42
|
+
maxTags?: number;
|
|
43
|
+
/** Permite tags duplicadas */
|
|
44
|
+
allowDuplicates?: boolean;
|
|
45
|
+
/** Evento quando o valor das tags e alterado */
|
|
46
|
+
onChangeValue?: (value: string[], event?: any) => void;
|
|
47
|
+
/** Evento quando o campo recebe o foco */
|
|
48
|
+
onFocusEnter?: FocusEventHandler<HTMLInputElement>;
|
|
49
|
+
/** Evento quando o foco sai do campo */
|
|
50
|
+
onFocusExit?: FocusEventHandler<HTMLInputElement>;
|
|
51
|
+
/** Referencia para o componente interno */
|
|
52
|
+
innerRef?: React.RefObject<HTMLInputElement>;
|
|
53
|
+
/** Estilo do campo */
|
|
54
|
+
style?: CSSProperties;
|
|
55
|
+
/** Classe CSS adicional */
|
|
56
|
+
className?: string;
|
|
57
|
+
/** Largura do campo */
|
|
58
|
+
width?: string | number;
|
|
59
|
+
/** Tamanho do campo (Mantine size) */
|
|
60
|
+
size?: MantineSize;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function ArchbaseTagInput<T, ID>({
|
|
64
|
+
dataSource,
|
|
65
|
+
dataField,
|
|
66
|
+
value: valueProp,
|
|
67
|
+
defaultValue,
|
|
68
|
+
delimiter = ',',
|
|
69
|
+
label,
|
|
70
|
+
description,
|
|
71
|
+
error,
|
|
72
|
+
placeholder,
|
|
73
|
+
required = false,
|
|
74
|
+
disabled = false,
|
|
75
|
+
readOnly = false,
|
|
76
|
+
separators,
|
|
77
|
+
maxTags,
|
|
78
|
+
allowDuplicates = false,
|
|
79
|
+
onChangeValue = () => {},
|
|
80
|
+
onFocusEnter = () => {},
|
|
81
|
+
onFocusExit = () => {},
|
|
82
|
+
innerRef,
|
|
83
|
+
style,
|
|
84
|
+
className,
|
|
85
|
+
width,
|
|
86
|
+
size,
|
|
87
|
+
}: ArchbaseTagInputProps<T, ID>) {
|
|
88
|
+
// Hook de compatibilidade V1/V2
|
|
89
|
+
const v1v2Compatibility = useArchbaseV1V2Compatibility<string[]>(
|
|
90
|
+
'ArchbaseTagInput',
|
|
91
|
+
dataSource,
|
|
92
|
+
dataField,
|
|
93
|
+
[],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const [tags, setTags] = useState<string[]>(valueProp || defaultValue || []);
|
|
97
|
+
const innerComponentRef = useRef<any>(null);
|
|
98
|
+
const [internalError, setInternalError] = useState<string | undefined>(error);
|
|
99
|
+
const forceUpdate = useForceUpdate();
|
|
100
|
+
/** Guarda se o valor original no dataSource e uma string (para serializar de volta) */
|
|
101
|
+
const originalIsStringRef = useRef<boolean>(false);
|
|
102
|
+
|
|
103
|
+
// Contexto de validacao (opcional - pode nao existir)
|
|
104
|
+
const validationContext = useValidationErrors();
|
|
105
|
+
|
|
106
|
+
// Chave unica para o field
|
|
107
|
+
const fieldKey = `${dataField}`;
|
|
108
|
+
|
|
109
|
+
// Recuperar erro do contexto se existir
|
|
110
|
+
const contextError = validationContext?.getError(fieldKey);
|
|
111
|
+
|
|
112
|
+
// Atualizar erro externo
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (error !== undefined && error !== internalError) {
|
|
115
|
+
setInternalError(error);
|
|
116
|
+
}
|
|
117
|
+
}, [error]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Converte o valor bruto do campo do dataSource em um array de strings.
|
|
121
|
+
* Se o valor for uma string, faz split pelo delimiter.
|
|
122
|
+
* Se for um array, usa diretamente.
|
|
123
|
+
*/
|
|
124
|
+
const parseFieldValue = (raw: any): string[] => {
|
|
125
|
+
if (raw === null || raw === undefined || raw === '') {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(raw)) {
|
|
129
|
+
originalIsStringRef.current = false;
|
|
130
|
+
return raw.map(String);
|
|
131
|
+
}
|
|
132
|
+
if (typeof raw === 'string') {
|
|
133
|
+
originalIsStringRef.current = true;
|
|
134
|
+
return raw
|
|
135
|
+
.split(delimiter)
|
|
136
|
+
.map((s) => s.trim())
|
|
137
|
+
.filter((s) => s.length > 0);
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Serializa o array de tags de volta para o formato original do campo no dataSource.
|
|
144
|
+
* Se o campo original era string, junta pelo delimiter; se era array, mantem como array.
|
|
145
|
+
*/
|
|
146
|
+
const serializeValue = (tagsArray: string[]): string | string[] => {
|
|
147
|
+
if (originalIsStringRef.current) {
|
|
148
|
+
return tagsArray.join(delimiter);
|
|
149
|
+
}
|
|
150
|
+
return tagsArray;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const loadDataSourceFieldValue = () => {
|
|
154
|
+
let initialValue: string[] = valueProp || defaultValue || [];
|
|
155
|
+
|
|
156
|
+
if (dataSource && dataField) {
|
|
157
|
+
const raw = dataSource.getFieldValue(dataField);
|
|
158
|
+
initialValue = parseFieldValue(raw);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setTags(initialValue);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const fieldChangedListener = useCallback(() => {
|
|
165
|
+
loadDataSourceFieldValue();
|
|
166
|
+
}, []);
|
|
167
|
+
|
|
168
|
+
const dataSourceEvent = useCallback(
|
|
169
|
+
(event: DataSourceEvent<T>) => {
|
|
170
|
+
if (dataSource && dataField) {
|
|
171
|
+
if (
|
|
172
|
+
event.type === DataSourceEventNames.dataChanged ||
|
|
173
|
+
event.type === DataSourceEventNames.recordChanged ||
|
|
174
|
+
event.type === DataSourceEventNames.afterScroll ||
|
|
175
|
+
event.type === DataSourceEventNames.afterCancel ||
|
|
176
|
+
event.type === DataSourceEventNames.afterEdit ||
|
|
177
|
+
event.type === DataSourceEventNames.afterInsert
|
|
178
|
+
) {
|
|
179
|
+
loadDataSourceFieldValue();
|
|
180
|
+
if (!v1v2Compatibility.isDataSourceV2) {
|
|
181
|
+
forceUpdate();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Limpa erros em eventos de edicao/cancelamento
|
|
186
|
+
if (
|
|
187
|
+
event.type === DataSourceEventNames.afterEdit ||
|
|
188
|
+
event.type === DataSourceEventNames.afterInsert ||
|
|
189
|
+
event.type === DataSourceEventNames.afterCancel
|
|
190
|
+
) {
|
|
191
|
+
setInternalError(undefined);
|
|
192
|
+
validationContext?.clearError(fieldKey);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
event.type === DataSourceEventNames.onFieldError &&
|
|
197
|
+
event.fieldName === dataField
|
|
198
|
+
) {
|
|
199
|
+
setInternalError(event.error);
|
|
200
|
+
validationContext?.setError(fieldKey, event.error);
|
|
201
|
+
if (!v1v2Compatibility.isDataSourceV2) {
|
|
202
|
+
forceUpdate();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
[
|
|
208
|
+
v1v2Compatibility.isDataSourceV2,
|
|
209
|
+
dataSource,
|
|
210
|
+
dataField,
|
|
211
|
+
loadDataSourceFieldValue,
|
|
212
|
+
forceUpdate,
|
|
213
|
+
validationContext,
|
|
214
|
+
fieldKey,
|
|
215
|
+
],
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Ref para manter callback sempre atualizado (corrige problema de closure desatualizada)
|
|
219
|
+
const dataSourceEventRef = useRef(dataSourceEvent);
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
dataSourceEventRef.current = dataSourceEvent;
|
|
222
|
+
}, [dataSourceEvent]);
|
|
223
|
+
|
|
224
|
+
// Wrapper estavel que delega para ref - nunca muda, entao o listener permanece consistente
|
|
225
|
+
const stableDataSourceEvent = useCallback((event: DataSourceEvent<T>) => {
|
|
226
|
+
dataSourceEventRef.current(event);
|
|
227
|
+
}, []);
|
|
228
|
+
|
|
229
|
+
// Registrar listeners com cleanup apropriado
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
loadDataSourceFieldValue();
|
|
232
|
+
if (dataSource && dataField) {
|
|
233
|
+
const hasFieldListener =
|
|
234
|
+
typeof (dataSource as any).addFieldChangeListener === 'function';
|
|
235
|
+
dataSource.addListener(stableDataSourceEvent);
|
|
236
|
+
if (hasFieldListener) {
|
|
237
|
+
(dataSource as any).addFieldChangeListener(dataField, fieldChangedListener);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return () => {
|
|
241
|
+
dataSource.removeListener(stableDataSourceEvent);
|
|
242
|
+
if (hasFieldListener) {
|
|
243
|
+
(dataSource as any).removeFieldChangeListener(
|
|
244
|
+
dataField,
|
|
245
|
+
fieldChangedListener,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}, [dataSource, dataField, stableDataSourceEvent, fieldChangedListener]);
|
|
251
|
+
|
|
252
|
+
useArchbaseDidUpdate(() => {
|
|
253
|
+
loadDataSourceFieldValue();
|
|
254
|
+
}, []);
|
|
255
|
+
|
|
256
|
+
const handleChange = (newTags: string[]) => {
|
|
257
|
+
// Limpa erro quando usuario edita o campo
|
|
258
|
+
const hasError = internalError || contextError;
|
|
259
|
+
if (hasError) {
|
|
260
|
+
setInternalError(undefined);
|
|
261
|
+
validationContext?.clearError(fieldKey);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
setTags(newTags);
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
dataSource &&
|
|
268
|
+
!dataSource.isBrowsing() &&
|
|
269
|
+
dataField
|
|
270
|
+
) {
|
|
271
|
+
const serialized = serializeValue(newTags);
|
|
272
|
+
v1v2Compatibility.handleValueChange(serialized as any);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (onChangeValue) {
|
|
276
|
+
onChangeValue(newTags);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleOnFocusExit = (event: React.FocusEvent<HTMLInputElement>) => {
|
|
281
|
+
if (onFocusExit) {
|
|
282
|
+
onFocusExit(event);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const handleOnFocusEnter = (event: React.FocusEvent<HTMLInputElement>) => {
|
|
287
|
+
if (onFocusEnter) {
|
|
288
|
+
onFocusEnter(event);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const isReadOnly = (): boolean => {
|
|
293
|
+
return readOnly || v1v2Compatibility.isReadOnly;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Erro a ser exibido: local ou do contexto
|
|
297
|
+
const displayError = internalError || contextError;
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<TagsInput
|
|
301
|
+
value={tags}
|
|
302
|
+
onChange={handleChange}
|
|
303
|
+
label={label}
|
|
304
|
+
description={description}
|
|
305
|
+
error={displayError}
|
|
306
|
+
placeholder={placeholder}
|
|
307
|
+
required={required}
|
|
308
|
+
disabled={disabled}
|
|
309
|
+
readOnly={isReadOnly()}
|
|
310
|
+
size={size}
|
|
311
|
+
className={className}
|
|
312
|
+
maxTags={maxTags}
|
|
313
|
+
allowDuplicates={allowDuplicates}
|
|
314
|
+
splitChars={separators}
|
|
315
|
+
ref={innerRef || innerComponentRef}
|
|
316
|
+
onFocus={handleOnFocusEnter}
|
|
317
|
+
onBlur={handleOnFocusExit}
|
|
318
|
+
style={{
|
|
319
|
+
width,
|
|
320
|
+
...style,
|
|
321
|
+
}}
|
|
322
|
+
/>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
ArchbaseTagInput.displayName = 'ArchbaseTagInput';
|