@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,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchbaseLightGrid — grid leve baseado em sheet-happens com integração ao DataSource.
|
|
3
|
+
* Ideal para exibição e edição tabular de alto desempenho com milhares de linhas.
|
|
4
|
+
* @status experimental
|
|
5
|
+
*/
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
+
import { Box, Text, useMantineTheme, useComputedColorScheme } from '@mantine/core';
|
|
8
|
+
import { useForceUpdate } from '@mantine/hooks';
|
|
9
|
+
import Sheet from 'sheet-happens';
|
|
10
|
+
import 'sheet-happens/dist/index.css';
|
|
11
|
+
import type { DataSourceEvent, IArchbaseDataSourceBase } from '@archbase/data';
|
|
12
|
+
import { DataSourceEventNames, useArchbaseDataSourceListener } from '@archbase/data';
|
|
13
|
+
import type { CSSProperties } from 'react';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Types
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export interface ArchbaseLightGridColumn<T> {
|
|
20
|
+
/** Campo do registro (keyof T ou path com notação de ponto) */
|
|
21
|
+
field: keyof T | string;
|
|
22
|
+
/** Texto do cabeçalho */
|
|
23
|
+
header: string;
|
|
24
|
+
/** Largura da coluna em pixels */
|
|
25
|
+
width?: number;
|
|
26
|
+
/** Tipo de dado da coluna */
|
|
27
|
+
type?: 'text' | 'number' | 'date' | 'boolean' | 'currency' | 'custom';
|
|
28
|
+
/** Formato (ex.: "DD/MM/YYYY", "0,0.00", "R$ #.##0,00") */
|
|
29
|
+
format?: string;
|
|
30
|
+
/** Se a coluna e editavel */
|
|
31
|
+
editable?: boolean;
|
|
32
|
+
/** Se a coluna deve ficar fixa a esquerda */
|
|
33
|
+
frozen?: boolean;
|
|
34
|
+
/** Alinhamento do texto */
|
|
35
|
+
align?: 'left' | 'center' | 'right';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ArchbaseLightGridProps<T, ID> {
|
|
39
|
+
/** Fonte de dados */
|
|
40
|
+
dataSource?: IArchbaseDataSourceBase<T>;
|
|
41
|
+
/** Definicao das colunas */
|
|
42
|
+
columns: ArchbaseLightGridColumn<T>[];
|
|
43
|
+
/** Dados standalone (usado quando nao ha dataSource) */
|
|
44
|
+
data?: T[];
|
|
45
|
+
/** Habilita edicao de celulas */
|
|
46
|
+
editable?: boolean;
|
|
47
|
+
/** Altura do grid */
|
|
48
|
+
height?: string | number;
|
|
49
|
+
/** Largura do grid */
|
|
50
|
+
width?: string | number;
|
|
51
|
+
/** Exibe numeracao de linhas */
|
|
52
|
+
showRowNumbers?: boolean;
|
|
53
|
+
/** Callback quando uma celula e alterada */
|
|
54
|
+
onCellChange?: (record: T, field: string, oldValue: any, newValue: any) => void;
|
|
55
|
+
/** Callback quando a selecao muda */
|
|
56
|
+
onSelectionChange?: (selection: { row: number; col: number }[]) => void;
|
|
57
|
+
/** Somente leitura (sobrescreve editable) */
|
|
58
|
+
readOnly?: boolean;
|
|
59
|
+
/** Label exibido acima do grid */
|
|
60
|
+
label?: string;
|
|
61
|
+
/** Descricao exibida abaixo do label */
|
|
62
|
+
description?: string;
|
|
63
|
+
/** Estilo CSS do container */
|
|
64
|
+
style?: CSSProperties;
|
|
65
|
+
/** Classe CSS do container */
|
|
66
|
+
className?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Helpers
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
function getNestedValue(obj: any, path: string): any {
|
|
74
|
+
if (!obj || !path) return undefined;
|
|
75
|
+
const parts = path.split('.');
|
|
76
|
+
let current = obj;
|
|
77
|
+
for (const part of parts) {
|
|
78
|
+
if (current == null) return undefined;
|
|
79
|
+
current = current[part];
|
|
80
|
+
}
|
|
81
|
+
return current;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setNestedValue(obj: any, path: string, value: any): void {
|
|
85
|
+
if (!obj || !path) return;
|
|
86
|
+
const parts = path.split('.');
|
|
87
|
+
let current = obj;
|
|
88
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
89
|
+
if (current[parts[i]] == null) {
|
|
90
|
+
current[parts[i]] = {};
|
|
91
|
+
}
|
|
92
|
+
current = current[parts[i]];
|
|
93
|
+
}
|
|
94
|
+
current[parts[parts.length - 1]] = value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatCellValue(
|
|
98
|
+
value: any,
|
|
99
|
+
type: ArchbaseLightGridColumn<any>['type'],
|
|
100
|
+
format?: string,
|
|
101
|
+
): string {
|
|
102
|
+
if (value == null) return '';
|
|
103
|
+
|
|
104
|
+
switch (type) {
|
|
105
|
+
case 'boolean':
|
|
106
|
+
return value ? 'Sim' : 'Nao';
|
|
107
|
+
|
|
108
|
+
case 'number':
|
|
109
|
+
if (typeof value === 'number') {
|
|
110
|
+
if (format) {
|
|
111
|
+
try {
|
|
112
|
+
const decimals = (format.split('.')[1] || '').length;
|
|
113
|
+
return value.toLocaleString('pt-BR', {
|
|
114
|
+
minimumFractionDigits: decimals,
|
|
115
|
+
maximumFractionDigits: decimals,
|
|
116
|
+
});
|
|
117
|
+
} catch {
|
|
118
|
+
return String(value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return value.toLocaleString('pt-BR');
|
|
122
|
+
}
|
|
123
|
+
return String(value);
|
|
124
|
+
|
|
125
|
+
case 'currency':
|
|
126
|
+
if (typeof value === 'number') {
|
|
127
|
+
return value.toLocaleString('pt-BR', {
|
|
128
|
+
style: 'currency',
|
|
129
|
+
currency: 'BRL',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return String(value);
|
|
133
|
+
|
|
134
|
+
case 'date':
|
|
135
|
+
if (value instanceof Date) {
|
|
136
|
+
return value.toLocaleDateString('pt-BR');
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === 'string') {
|
|
139
|
+
try {
|
|
140
|
+
const d = new Date(value);
|
|
141
|
+
if (!isNaN(d.getTime())) {
|
|
142
|
+
return d.toLocaleDateString('pt-BR');
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// fall through
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return String(value);
|
|
149
|
+
|
|
150
|
+
case 'text':
|
|
151
|
+
default:
|
|
152
|
+
return String(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseCellInput(
|
|
157
|
+
input: string,
|
|
158
|
+
type: ArchbaseLightGridColumn<any>['type'],
|
|
159
|
+
): any {
|
|
160
|
+
if (input === '' || input == null) return null;
|
|
161
|
+
|
|
162
|
+
switch (type) {
|
|
163
|
+
case 'boolean':
|
|
164
|
+
return (
|
|
165
|
+
input.toLowerCase() === 'true' ||
|
|
166
|
+
input.toLowerCase() === 'sim' ||
|
|
167
|
+
input === '1'
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
case 'number':
|
|
171
|
+
case 'currency': {
|
|
172
|
+
const cleaned = input.replace(/[R$\s.]/g, '').replace(',', '.');
|
|
173
|
+
const num = Number(cleaned);
|
|
174
|
+
return isNaN(num) ? input : num;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case 'date': {
|
|
178
|
+
// Tenta parsear DD/MM/YYYY
|
|
179
|
+
const parts = input.split('/');
|
|
180
|
+
if (parts.length === 3) {
|
|
181
|
+
const d = new Date(Number(parts[2]), Number(parts[1]) - 1, Number(parts[0]));
|
|
182
|
+
if (!isNaN(d.getTime())) return d.toISOString();
|
|
183
|
+
}
|
|
184
|
+
// Tenta ISO
|
|
185
|
+
const iso = new Date(input);
|
|
186
|
+
if (!isNaN(iso.getTime())) return iso.toISOString();
|
|
187
|
+
return input;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
default:
|
|
191
|
+
return input;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Component
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
function ArchbaseLightGridInner<T, ID>(
|
|
200
|
+
props: ArchbaseLightGridProps<T, ID>,
|
|
201
|
+
) {
|
|
202
|
+
const {
|
|
203
|
+
dataSource,
|
|
204
|
+
columns,
|
|
205
|
+
data: standaloneData,
|
|
206
|
+
editable = false,
|
|
207
|
+
height = '400px',
|
|
208
|
+
width = '100%',
|
|
209
|
+
showRowNumbers = true,
|
|
210
|
+
onCellChange,
|
|
211
|
+
onSelectionChange,
|
|
212
|
+
readOnly = false,
|
|
213
|
+
label,
|
|
214
|
+
description,
|
|
215
|
+
style,
|
|
216
|
+
className,
|
|
217
|
+
} = props;
|
|
218
|
+
|
|
219
|
+
const theme = useMantineTheme();
|
|
220
|
+
const computedColorScheme = useComputedColorScheme('light');
|
|
221
|
+
const isDark = computedColorScheme === 'dark';
|
|
222
|
+
const forceUpdate = useForceUpdate();
|
|
223
|
+
|
|
224
|
+
const [records, setRecords] = useState<T[]>([]);
|
|
225
|
+
const [selectedRow, setSelectedRow] = useState<number>(0);
|
|
226
|
+
const recordsRef = useRef<T[]>([]);
|
|
227
|
+
|
|
228
|
+
// Sincroniza dados do DataSource ou standalone
|
|
229
|
+
const syncRecords = useCallback(() => {
|
|
230
|
+
if (dataSource) {
|
|
231
|
+
const dsRecords = dataSource.browseRecords() || [];
|
|
232
|
+
recordsRef.current = dsRecords;
|
|
233
|
+
setRecords(dsRecords);
|
|
234
|
+
} else if (standaloneData) {
|
|
235
|
+
recordsRef.current = standaloneData;
|
|
236
|
+
setRecords(standaloneData);
|
|
237
|
+
}
|
|
238
|
+
}, [dataSource, standaloneData]);
|
|
239
|
+
|
|
240
|
+
// Numero de colunas congeladas
|
|
241
|
+
const frozenCols = useMemo(
|
|
242
|
+
() => columns.filter((c) => c.frozen).length,
|
|
243
|
+
[columns],
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Listener do DataSource
|
|
247
|
+
const dataSourceEvent = useCallback(
|
|
248
|
+
(event: DataSourceEvent<T>) => {
|
|
249
|
+
if (
|
|
250
|
+
event.type === DataSourceEventNames.dataChanged ||
|
|
251
|
+
event.type === DataSourceEventNames.recordChanged ||
|
|
252
|
+
event.type === DataSourceEventNames.refreshData ||
|
|
253
|
+
event.type === DataSourceEventNames.afterAppend ||
|
|
254
|
+
event.type === DataSourceEventNames.afterRemove ||
|
|
255
|
+
event.type === DataSourceEventNames.afterInsert ||
|
|
256
|
+
event.type === DataSourceEventNames.afterSave ||
|
|
257
|
+
event.type === DataSourceEventNames.afterCancel ||
|
|
258
|
+
event.type === DataSourceEventNames.afterEdit
|
|
259
|
+
) {
|
|
260
|
+
syncRecords();
|
|
261
|
+
forceUpdate();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (event.type === DataSourceEventNames.afterScroll) {
|
|
265
|
+
if (dataSource) {
|
|
266
|
+
const idx = dataSource.getCurrentIndex();
|
|
267
|
+
setSelectedRow(idx);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (event.type === DataSourceEventNames.fieldChanged) {
|
|
272
|
+
syncRecords();
|
|
273
|
+
forceUpdate();
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
[dataSource, syncRecords, forceUpdate],
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Ref estavel para o listener
|
|
280
|
+
const dataSourceEventRef = useRef(dataSourceEvent);
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
dataSourceEventRef.current = dataSourceEvent;
|
|
283
|
+
}, [dataSourceEvent]);
|
|
284
|
+
|
|
285
|
+
const stableListener = useCallback((event: DataSourceEvent<T>) => {
|
|
286
|
+
dataSourceEventRef.current(event);
|
|
287
|
+
}, []);
|
|
288
|
+
|
|
289
|
+
// Registra listener no DataSource
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (dataSource) {
|
|
292
|
+
dataSource.addListener(stableListener);
|
|
293
|
+
syncRecords();
|
|
294
|
+
setSelectedRow(dataSource.getCurrentIndex());
|
|
295
|
+
}
|
|
296
|
+
return () => {
|
|
297
|
+
if (dataSource) {
|
|
298
|
+
dataSource.removeListener(stableListener);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}, [(dataSource as any)?.uuid || dataSource?.getName()]);
|
|
302
|
+
|
|
303
|
+
// Sincroniza dados standalone
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (!dataSource && standaloneData) {
|
|
306
|
+
syncRecords();
|
|
307
|
+
}
|
|
308
|
+
}, [standaloneData, dataSource]);
|
|
309
|
+
|
|
310
|
+
// -----------------------------------------------------------------------
|
|
311
|
+
// sheet-happens callbacks
|
|
312
|
+
// -----------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
const cellWidth = useCallback(
|
|
315
|
+
(col: number): number => {
|
|
316
|
+
if (col < 0 || col >= columns.length) return 100;
|
|
317
|
+
return columns[col].width ?? 100;
|
|
318
|
+
},
|
|
319
|
+
[columns],
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const cellHeight = useCallback((): number => {
|
|
323
|
+
return 28;
|
|
324
|
+
}, []);
|
|
325
|
+
|
|
326
|
+
const sourceData = useCallback(
|
|
327
|
+
(row: number, col: number): string => {
|
|
328
|
+
if (row < 0 || row >= records.length) return '';
|
|
329
|
+
if (col < 0 || col >= columns.length) return '';
|
|
330
|
+
|
|
331
|
+
const record = records[row];
|
|
332
|
+
const column = columns[col];
|
|
333
|
+
const field = String(column.field);
|
|
334
|
+
const rawValue = getNestedValue(record, field);
|
|
335
|
+
|
|
336
|
+
return formatCellValue(rawValue, column.type, column.format);
|
|
337
|
+
},
|
|
338
|
+
[records, columns],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// editData is a getter in sheet-happens: (row, col) => string
|
|
342
|
+
// Returns the current editing value for a cell (empty string for no edit state)
|
|
343
|
+
const editDataMap = useRef<Record<string, string>>({});
|
|
344
|
+
|
|
345
|
+
const editData = useCallback(
|
|
346
|
+
(row: number, col: number): string => {
|
|
347
|
+
const key = `${row}:${col}`;
|
|
348
|
+
return editDataMap.current[key] ?? '';
|
|
349
|
+
},
|
|
350
|
+
[],
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// onChange handles cell value changes in sheet-happens
|
|
354
|
+
const handleCellChanges = useCallback(
|
|
355
|
+
(changes: Array<{ x: number; y: number; value: string | number }>) => {
|
|
356
|
+
for (const change of changes) {
|
|
357
|
+
const { x: col, y: row, value: rawValue } = change;
|
|
358
|
+
const value = String(rawValue);
|
|
359
|
+
if (readOnly || !editable) return;
|
|
360
|
+
if (row < 0 || row >= records.length) return;
|
|
361
|
+
if (col < 0 || col >= columns.length) return;
|
|
362
|
+
|
|
363
|
+
const column = columns[col];
|
|
364
|
+
if (column.editable === false) return;
|
|
365
|
+
|
|
366
|
+
const record = records[row];
|
|
367
|
+
const field = String(column.field);
|
|
368
|
+
const oldValue = getNestedValue(record, field);
|
|
369
|
+
const newValue = parseCellInput(value, column.type);
|
|
370
|
+
|
|
371
|
+
if (dataSource) {
|
|
372
|
+
if (dataSource.getCurrentIndex() !== row) {
|
|
373
|
+
dataSource.gotoRecord(row);
|
|
374
|
+
}
|
|
375
|
+
dataSource.setFieldValue(field, newValue);
|
|
376
|
+
} else {
|
|
377
|
+
setNestedValue(record, field, newValue);
|
|
378
|
+
setRecords([...records]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (onCellChange) {
|
|
382
|
+
onCellChange(record, field, oldValue, newValue);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
[readOnly, editable, records, columns, dataSource, onCellChange],
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const isReadOnly = useCallback(
|
|
390
|
+
(row: number, col: number): boolean => {
|
|
391
|
+
if (readOnly || !editable) return true;
|
|
392
|
+
if (col < 0 || col >= columns.length) return true;
|
|
393
|
+
const column = columns[col];
|
|
394
|
+
return column.editable === false;
|
|
395
|
+
},
|
|
396
|
+
[readOnly, editable, columns],
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Headers
|
|
400
|
+
const headerData = useCallback(
|
|
401
|
+
(col: number): string => {
|
|
402
|
+
if (col < 0 || col >= columns.length) return '';
|
|
403
|
+
return columns[col].header;
|
|
404
|
+
},
|
|
405
|
+
[columns],
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// Selection change
|
|
409
|
+
const onSelectionChanged = useCallback(
|
|
410
|
+
(
|
|
411
|
+
x1: number,
|
|
412
|
+
y1: number,
|
|
413
|
+
x2: number,
|
|
414
|
+
y2: number,
|
|
415
|
+
) => {
|
|
416
|
+
// Navega para a linha selecionada no DataSource
|
|
417
|
+
if (dataSource && y1 >= 0 && y1 < records.length) {
|
|
418
|
+
if (dataSource.getCurrentIndex() !== y1) {
|
|
419
|
+
dataSource.gotoRecord(y1);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
setSelectedRow(y1);
|
|
423
|
+
|
|
424
|
+
if (onSelectionChange) {
|
|
425
|
+
const selection: { row: number; col: number }[] = [];
|
|
426
|
+
for (let r = Math.min(y1, y2); r <= Math.max(y1, y2); r++) {
|
|
427
|
+
for (let c = Math.min(x1, x2); c <= Math.max(x1, x2); c++) {
|
|
428
|
+
selection.push({ row: r, col: c });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
onSelectionChange(selection);
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
[dataSource, records.length, onSelectionChange],
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// Cell style para dark mode e alinhamento
|
|
438
|
+
const cellStyle = useCallback(
|
|
439
|
+
(
|
|
440
|
+
x: number,
|
|
441
|
+
y: number,
|
|
442
|
+
): Record<string, any> => {
|
|
443
|
+
const style: Record<string, any> = {};
|
|
444
|
+
|
|
445
|
+
if (isDark) {
|
|
446
|
+
style.color = theme.colors.dark[0];
|
|
447
|
+
style.backgroundColor =
|
|
448
|
+
y === selectedRow
|
|
449
|
+
? theme.colors.dark[5]
|
|
450
|
+
: theme.colors.dark[7];
|
|
451
|
+
} else {
|
|
452
|
+
style.backgroundColor =
|
|
453
|
+
y === selectedRow
|
|
454
|
+
? theme.colors.blue[0]
|
|
455
|
+
: '#ffffff';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (x >= 0 && x < columns.length) {
|
|
459
|
+
const column = columns[x];
|
|
460
|
+
if (column.align) {
|
|
461
|
+
style.textAlign = column.align;
|
|
462
|
+
} else if (
|
|
463
|
+
column.type === 'number' ||
|
|
464
|
+
column.type === 'currency'
|
|
465
|
+
) {
|
|
466
|
+
style.textAlign = 'right';
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return style;
|
|
471
|
+
},
|
|
472
|
+
[isDark, theme, columns, selectedRow],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Header style para dark mode
|
|
476
|
+
const headerStyle = useCallback(
|
|
477
|
+
(): Record<string, any> => {
|
|
478
|
+
if (isDark) {
|
|
479
|
+
return {
|
|
480
|
+
color: theme.colors.dark[0],
|
|
481
|
+
backgroundColor: theme.colors.dark[6],
|
|
482
|
+
fontWeight: 600,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
fontWeight: 600,
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
[isDark, theme],
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
// -----------------------------------------------------------------------
|
|
493
|
+
// Render
|
|
494
|
+
// -----------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
const containerStyle: CSSProperties = {
|
|
497
|
+
width,
|
|
498
|
+
height,
|
|
499
|
+
display: 'flex',
|
|
500
|
+
flexDirection: 'column',
|
|
501
|
+
...style,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<Box className={className} style={containerStyle}>
|
|
506
|
+
{label && (
|
|
507
|
+
<Text fw={500} size="sm" mb={2}>
|
|
508
|
+
{label}
|
|
509
|
+
</Text>
|
|
510
|
+
)}
|
|
511
|
+
{description && (
|
|
512
|
+
<Text size="xs" c="dimmed" mb={4}>
|
|
513
|
+
{description}
|
|
514
|
+
</Text>
|
|
515
|
+
)}
|
|
516
|
+
<Box style={{ flex: 1, minHeight: 0, overflow: 'hidden' }}>
|
|
517
|
+
<Sheet
|
|
518
|
+
sourceData={sourceData}
|
|
519
|
+
editData={editData}
|
|
520
|
+
cellWidth={cellWidth}
|
|
521
|
+
cellHeight={cellHeight}
|
|
522
|
+
columnHeaders={headerData}
|
|
523
|
+
cellStyle={cellStyle}
|
|
524
|
+
readOnly={isReadOnly}
|
|
525
|
+
onChange={handleCellChanges}
|
|
526
|
+
onSelectionChanged={onSelectionChanged}
|
|
527
|
+
sheetStyle={{
|
|
528
|
+
freezeColumns: frozenCols,
|
|
529
|
+
} as any}
|
|
530
|
+
/>
|
|
531
|
+
</Box>
|
|
532
|
+
</Box>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export const ArchbaseLightGrid = React.memo(ArchbaseLightGridInner) as typeof ArchbaseLightGridInner;
|
|
537
|
+
|
|
538
|
+
(ArchbaseLightGrid as any).displayName = 'ArchbaseLightGrid';
|