@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,433 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Box, Input, useMantineTheme, useComputedColorScheme } from '@mantine/core';
|
|
3
|
+
import {
|
|
4
|
+
useArchbaseDataSourceListener,
|
|
5
|
+
type DataSourceEvent,
|
|
6
|
+
DataSourceEventNames,
|
|
7
|
+
type IArchbaseDataSourceBase,
|
|
8
|
+
} from '@archbase/data';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export type ArchbaseResourceTimelineViewMode = 'day' | 'week' | 'month' | 'quarter';
|
|
15
|
+
|
|
16
|
+
export interface ArchbaseResourceTimelineProps<TResource, TEvent, ID = any> {
|
|
17
|
+
/** DataSource for resources (groups/rows). */
|
|
18
|
+
resourceDataSource?: IArchbaseDataSourceBase<TResource>;
|
|
19
|
+
/** Field that holds the resource id. */
|
|
20
|
+
resourceIdField?: string;
|
|
21
|
+
/** Field that holds the resource display label. */
|
|
22
|
+
resourceLabelField?: string;
|
|
23
|
+
/** DataSource for events (items). */
|
|
24
|
+
eventDataSource?: IArchbaseDataSourceBase<TEvent>;
|
|
25
|
+
/** Field that holds the event start date. */
|
|
26
|
+
eventStartField?: string;
|
|
27
|
+
/** Field that holds the event end date. */
|
|
28
|
+
eventEndField?: string;
|
|
29
|
+
/** Field that links the event to a resource. */
|
|
30
|
+
eventResourceIdField?: string;
|
|
31
|
+
/** Field that holds the event display label. */
|
|
32
|
+
eventLabelField?: string;
|
|
33
|
+
/** Optional field that holds a CSS color for the event. */
|
|
34
|
+
eventColorField?: string;
|
|
35
|
+
/** Standalone resource array (used when no resourceDataSource). */
|
|
36
|
+
resources?: TResource[];
|
|
37
|
+
/** Standalone event array (used when no eventDataSource). */
|
|
38
|
+
events?: TEvent[];
|
|
39
|
+
/** Visible range start. */
|
|
40
|
+
startDate?: Date;
|
|
41
|
+
/** Visible range end. */
|
|
42
|
+
endDate?: Date;
|
|
43
|
+
/** Zoom / time-axis granularity preset. */
|
|
44
|
+
viewMode?: ArchbaseResourceTimelineViewMode;
|
|
45
|
+
/** Allow drag & resize of events. */
|
|
46
|
+
editable?: boolean;
|
|
47
|
+
/** Fired when the user clicks an event. */
|
|
48
|
+
onEventClick?: (event: TEvent) => void;
|
|
49
|
+
/** Fired after an event is moved (drag). */
|
|
50
|
+
onEventMove?: (event: TEvent, newStart: Date, newEnd: Date, newResourceId: any) => void;
|
|
51
|
+
/** Fired after an event is resized. */
|
|
52
|
+
onEventResize?: (event: TEvent, newStart: Date, newEnd: Date) => void;
|
|
53
|
+
/** Fired when the user clicks on an empty slot. */
|
|
54
|
+
onSlotClick?: (resourceId: any, date: Date) => void;
|
|
55
|
+
/** Fired when a new event range is selected via drag on an empty area. */
|
|
56
|
+
onCreateEvent?: (resourceId: any, start: Date, end: Date) => void;
|
|
57
|
+
/** Input.Wrapper label. */
|
|
58
|
+
label?: string;
|
|
59
|
+
/** Input.Wrapper description. */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Height of the timeline container. */
|
|
62
|
+
height?: string | number;
|
|
63
|
+
/** Custom inline style for the outer wrapper. */
|
|
64
|
+
style?: React.CSSProperties;
|
|
65
|
+
/** Custom CSS class for the outer wrapper. */
|
|
66
|
+
className?: string;
|
|
67
|
+
/** Width of the outer wrapper. */
|
|
68
|
+
width?: string | number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Helpers
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/** Safely read a nested field like "a.b.c" from an object. */
|
|
76
|
+
function getField(obj: any, path: string): any {
|
|
77
|
+
if (!obj || !path) return undefined;
|
|
78
|
+
return path.split('.').reduce((cur, key) => cur?.[key], obj);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Build vis-timeline options for a given viewMode. */
|
|
82
|
+
function buildViewModeOptions(viewMode: ArchbaseResourceTimelineViewMode | undefined) {
|
|
83
|
+
switch (viewMode) {
|
|
84
|
+
case 'day':
|
|
85
|
+
return {
|
|
86
|
+
timeAxis: { scale: 'hour', step: 1 },
|
|
87
|
+
zoomMin: 1000 * 60 * 60, // 1 hour
|
|
88
|
+
zoomMax: 1000 * 60 * 60 * 24 * 3, // 3 days
|
|
89
|
+
};
|
|
90
|
+
case 'week':
|
|
91
|
+
return {
|
|
92
|
+
timeAxis: { scale: 'day', step: 1 },
|
|
93
|
+
zoomMin: 1000 * 60 * 60 * 24, // 1 day
|
|
94
|
+
zoomMax: 1000 * 60 * 60 * 24 * 14, // 14 days
|
|
95
|
+
};
|
|
96
|
+
case 'month':
|
|
97
|
+
return {
|
|
98
|
+
timeAxis: { scale: 'day', step: 1 },
|
|
99
|
+
zoomMin: 1000 * 60 * 60 * 24 * 7, // 7 days
|
|
100
|
+
zoomMax: 1000 * 60 * 60 * 24 * 62, // ~2 months
|
|
101
|
+
};
|
|
102
|
+
case 'quarter':
|
|
103
|
+
return {
|
|
104
|
+
timeAxis: { scale: 'week', step: 1 },
|
|
105
|
+
zoomMin: 1000 * 60 * 60 * 24 * 30, // 1 month
|
|
106
|
+
zoomMax: 1000 * 60 * 60 * 24 * 180, // ~6 months
|
|
107
|
+
};
|
|
108
|
+
default:
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Component
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
function ArchbaseResourceTimeline<TResource, TEvent, ID = any>({
|
|
118
|
+
resourceDataSource,
|
|
119
|
+
resourceIdField = 'id',
|
|
120
|
+
resourceLabelField = 'name',
|
|
121
|
+
eventDataSource,
|
|
122
|
+
eventStartField = 'startDate',
|
|
123
|
+
eventEndField = 'endDate',
|
|
124
|
+
eventResourceIdField = 'resourceId',
|
|
125
|
+
eventLabelField = 'title',
|
|
126
|
+
eventColorField,
|
|
127
|
+
resources: resourcesProp,
|
|
128
|
+
events: eventsProp,
|
|
129
|
+
startDate,
|
|
130
|
+
endDate,
|
|
131
|
+
viewMode,
|
|
132
|
+
editable = false,
|
|
133
|
+
onEventClick,
|
|
134
|
+
onEventMove,
|
|
135
|
+
onEventResize,
|
|
136
|
+
onSlotClick,
|
|
137
|
+
onCreateEvent,
|
|
138
|
+
label,
|
|
139
|
+
description,
|
|
140
|
+
height = '400px',
|
|
141
|
+
style,
|
|
142
|
+
className,
|
|
143
|
+
width,
|
|
144
|
+
}: ArchbaseResourceTimelineProps<TResource, TEvent, ID>) {
|
|
145
|
+
const theme = useMantineTheme();
|
|
146
|
+
const colorScheme = useComputedColorScheme('light');
|
|
147
|
+
const isDark = colorScheme === 'dark';
|
|
148
|
+
|
|
149
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
150
|
+
const timelineRef = useRef<any>(null);
|
|
151
|
+
const dataSetRef = useRef<{ items: any; groups: any }>({ items: null, groups: null });
|
|
152
|
+
|
|
153
|
+
// Keep latest callbacks in refs so vis-timeline handlers always see the
|
|
154
|
+
// current version without needing to re-register event listeners.
|
|
155
|
+
const callbackRefs = useRef({
|
|
156
|
+
onEventClick,
|
|
157
|
+
onEventMove,
|
|
158
|
+
onEventResize,
|
|
159
|
+
onSlotClick,
|
|
160
|
+
onCreateEvent,
|
|
161
|
+
});
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
callbackRefs.current = { onEventClick, onEventMove, onEventResize, onSlotClick, onCreateEvent };
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ------------------------------------------------------------------
|
|
167
|
+
// Derive resources & events arrays
|
|
168
|
+
// ------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
const [resourceUpdateKey, setResourceUpdateKey] = useState(0);
|
|
171
|
+
const [eventUpdateKey, setEventUpdateKey] = useState(0);
|
|
172
|
+
|
|
173
|
+
const rawResources = useMemo(() => {
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
175
|
+
resourceUpdateKey; // trigger re-compute
|
|
176
|
+
if (resourceDataSource) {
|
|
177
|
+
return resourceDataSource.browseRecords();
|
|
178
|
+
}
|
|
179
|
+
return resourcesProp ?? [];
|
|
180
|
+
}, [resourceDataSource, resourcesProp, resourceUpdateKey]);
|
|
181
|
+
|
|
182
|
+
const rawEvents = useMemo(() => {
|
|
183
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
184
|
+
eventUpdateKey; // trigger re-compute
|
|
185
|
+
if (eventDataSource) {
|
|
186
|
+
return eventDataSource.browseRecords();
|
|
187
|
+
}
|
|
188
|
+
return eventsProp ?? [];
|
|
189
|
+
}, [eventDataSource, eventsProp, eventUpdateKey]);
|
|
190
|
+
|
|
191
|
+
// ------------------------------------------------------------------
|
|
192
|
+
// Convert to vis-timeline format
|
|
193
|
+
// ------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
const groups = useMemo(() => {
|
|
196
|
+
return rawResources.map((r: any) => ({
|
|
197
|
+
id: getField(r, resourceIdField),
|
|
198
|
+
content: String(getField(r, resourceLabelField) ?? ''),
|
|
199
|
+
}));
|
|
200
|
+
}, [rawResources, resourceIdField, resourceLabelField]);
|
|
201
|
+
|
|
202
|
+
const items = useMemo(() => {
|
|
203
|
+
return rawEvents.map((e: any, idx: number) => {
|
|
204
|
+
const color = eventColorField ? getField(e, eventColorField) : undefined;
|
|
205
|
+
const itemStyle = color
|
|
206
|
+
? `background-color: ${color}; border-color: ${color};`
|
|
207
|
+
: undefined;
|
|
208
|
+
return {
|
|
209
|
+
id: getField(e, 'id') ?? idx,
|
|
210
|
+
group: getField(e, eventResourceIdField),
|
|
211
|
+
start: new Date(getField(e, eventStartField)),
|
|
212
|
+
end: getField(e, eventEndField) ? new Date(getField(e, eventEndField)) : undefined,
|
|
213
|
+
content: String(getField(e, eventLabelField) ?? ''),
|
|
214
|
+
style: itemStyle,
|
|
215
|
+
type: getField(e, eventEndField) ? 'range' : 'point',
|
|
216
|
+
_source: e,
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}, [rawEvents, eventResourceIdField, eventStartField, eventEndField, eventLabelField, eventColorField]);
|
|
220
|
+
|
|
221
|
+
// ------------------------------------------------------------------
|
|
222
|
+
// DataSource listeners
|
|
223
|
+
// ------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
const resourceDSListener = useCallback((_event: DataSourceEvent<TResource>) => {
|
|
226
|
+
setResourceUpdateKey((k) => k + 1);
|
|
227
|
+
}, []);
|
|
228
|
+
|
|
229
|
+
const eventDSListener = useCallback((_event: DataSourceEvent<TEvent>) => {
|
|
230
|
+
setEventUpdateKey((k) => k + 1);
|
|
231
|
+
}, []);
|
|
232
|
+
|
|
233
|
+
useArchbaseDataSourceListener({ dataSource: resourceDataSource, listener: resourceDSListener });
|
|
234
|
+
useArchbaseDataSourceListener({ dataSource: eventDataSource, listener: eventDSListener });
|
|
235
|
+
|
|
236
|
+
// ------------------------------------------------------------------
|
|
237
|
+
// Dark-mode colours
|
|
238
|
+
// ------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
const darkCss = useMemo(() => {
|
|
241
|
+
if (!isDark) return '';
|
|
242
|
+
const bg = theme.colors.dark[7];
|
|
243
|
+
const fg = theme.colors.dark[0];
|
|
244
|
+
const border = theme.colors.dark[4];
|
|
245
|
+
return `
|
|
246
|
+
.vis-timeline { background: ${bg}; border-color: ${border}; color: ${fg}; }
|
|
247
|
+
.vis-timeline .vis-panel.vis-bottom,
|
|
248
|
+
.vis-timeline .vis-panel.vis-left,
|
|
249
|
+
.vis-timeline .vis-panel.vis-right,
|
|
250
|
+
.vis-timeline .vis-panel.vis-top,
|
|
251
|
+
.vis-timeline .vis-panel.vis-center { border-color: ${border}; }
|
|
252
|
+
.vis-timeline .vis-time-axis .vis-text { color: ${fg}; }
|
|
253
|
+
.vis-timeline .vis-labelset .vis-label { color: ${fg}; background: ${bg}; border-color: ${border}; }
|
|
254
|
+
.vis-timeline .vis-foreground .vis-group { border-color: ${border}; }
|
|
255
|
+
.vis-timeline .vis-item { color: ${fg}; border-color: ${border}; }
|
|
256
|
+
.vis-timeline .vis-item.vis-selected { border-color: ${theme.colors[theme.primaryColor][5]}; }
|
|
257
|
+
.vis-timeline .vis-current-time { background-color: ${theme.colors.red[5]}; }
|
|
258
|
+
`;
|
|
259
|
+
}, [isDark, theme]);
|
|
260
|
+
|
|
261
|
+
// ------------------------------------------------------------------
|
|
262
|
+
// Create & manage vis-timeline instance
|
|
263
|
+
// ------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
if (!containerRef.current) return;
|
|
267
|
+
let mounted = true;
|
|
268
|
+
|
|
269
|
+
const init = async () => {
|
|
270
|
+
try {
|
|
271
|
+
// @ts-ignore dynamic import
|
|
272
|
+
const visModule = await import('vis-timeline');
|
|
273
|
+
const { Timeline } = visModule;
|
|
274
|
+
// @ts-ignore DataSet may be in different places
|
|
275
|
+
const DataSet = (visModule as any).DataSet ?? visModule.default?.DataSet;
|
|
276
|
+
|
|
277
|
+
if (!mounted || !containerRef.current) return;
|
|
278
|
+
|
|
279
|
+
const groupsDS = new DataSet(groups);
|
|
280
|
+
const itemsDS = new DataSet(items);
|
|
281
|
+
dataSetRef.current = { items: itemsDS, groups: groupsDS };
|
|
282
|
+
|
|
283
|
+
const viewOpts = buildViewModeOptions(viewMode);
|
|
284
|
+
|
|
285
|
+
const options: Record<string, any> = {
|
|
286
|
+
...viewOpts,
|
|
287
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
288
|
+
editable: editable
|
|
289
|
+
? { add: false, updateTime: true, updateGroup: true, remove: false }
|
|
290
|
+
: false,
|
|
291
|
+
selectable: true,
|
|
292
|
+
showCurrentTime: true,
|
|
293
|
+
orientation: { axis: 'top' },
|
|
294
|
+
groupOrder: 'id',
|
|
295
|
+
snap: null,
|
|
296
|
+
...(startDate ? { start: startDate } : {}),
|
|
297
|
+
...(endDate ? { end: endDate } : {}),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const tl = new Timeline(containerRef.current, itemsDS, groupsDS, options);
|
|
301
|
+
timelineRef.current = tl;
|
|
302
|
+
|
|
303
|
+
// --- Events ---
|
|
304
|
+
|
|
305
|
+
tl.on('select', (props: any) => {
|
|
306
|
+
const selectedIds: any[] = props.items ?? [];
|
|
307
|
+
if (selectedIds.length > 0) {
|
|
308
|
+
const selectedId = selectedIds[0];
|
|
309
|
+
const itemData = itemsDS.get(selectedId) as any;
|
|
310
|
+
if (itemData?._source) {
|
|
311
|
+
callbackRefs.current.onEventClick?.(itemData._source);
|
|
312
|
+
// Sync eventDataSource
|
|
313
|
+
if (eventDataSource) {
|
|
314
|
+
try {
|
|
315
|
+
eventDataSource.gotoRecordByData(itemData._source);
|
|
316
|
+
} catch {
|
|
317
|
+
// ignore navigation errors
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
tl.on('click', (props: any) => {
|
|
325
|
+
if (!props.item && props.group != null && props.time) {
|
|
326
|
+
callbackRefs.current.onSlotClick?.(props.group, new Date(props.time));
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
if (editable) {
|
|
331
|
+
// onMoving fires during drag, onMove fires on drop
|
|
332
|
+
(tl as any).on('move', (itemData: any, callback: (item: any | null) => void) => {
|
|
333
|
+
// Accept the move
|
|
334
|
+
callback(itemData);
|
|
335
|
+
if (itemData._source) {
|
|
336
|
+
callbackRefs.current.onEventMove?.(
|
|
337
|
+
itemData._source,
|
|
338
|
+
new Date(itemData.start),
|
|
339
|
+
new Date(itemData.end),
|
|
340
|
+
itemData.group,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// rangeselect for creating new events
|
|
347
|
+
tl.on('rangechanged' as any, () => {
|
|
348
|
+
// no-op, just prevent warnings
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// We use the "select" + Shift or a double-click on empty space for create
|
|
352
|
+
tl.on('doubleClick', (props: any) => {
|
|
353
|
+
if (!props.item && props.group != null && props.time) {
|
|
354
|
+
const start = new Date(props.time);
|
|
355
|
+
const end = new Date(start.getTime() + 1000 * 60 * 60); // +1 hour default
|
|
356
|
+
callbackRefs.current.onCreateEvent?.(props.group, start, end);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.error('[ArchbaseResourceTimeline] Failed to load vis-timeline:', err);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
init();
|
|
365
|
+
|
|
366
|
+
return () => {
|
|
367
|
+
mounted = false;
|
|
368
|
+
if (timelineRef.current) {
|
|
369
|
+
timelineRef.current.destroy();
|
|
370
|
+
timelineRef.current = null;
|
|
371
|
+
}
|
|
372
|
+
dataSetRef.current = { items: null, groups: null };
|
|
373
|
+
};
|
|
374
|
+
// Re-create when structural props change
|
|
375
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
376
|
+
}, [editable, viewMode]);
|
|
377
|
+
|
|
378
|
+
// ------------------------------------------------------------------
|
|
379
|
+
// Sync items & groups into existing DataSets
|
|
380
|
+
// ------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
const ds = dataSetRef.current.items;
|
|
384
|
+
if (!ds) return;
|
|
385
|
+
ds.clear();
|
|
386
|
+
ds.add(items);
|
|
387
|
+
}, [items]);
|
|
388
|
+
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
const ds = dataSetRef.current.groups;
|
|
391
|
+
if (!ds) return;
|
|
392
|
+
ds.clear();
|
|
393
|
+
ds.add(groups);
|
|
394
|
+
}, [groups]);
|
|
395
|
+
|
|
396
|
+
// Sync visible window
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
if (timelineRef.current && startDate && endDate) {
|
|
399
|
+
timelineRef.current.setWindow(startDate, endDate);
|
|
400
|
+
}
|
|
401
|
+
}, [startDate, endDate]);
|
|
402
|
+
|
|
403
|
+
// ------------------------------------------------------------------
|
|
404
|
+
// Render
|
|
405
|
+
// ------------------------------------------------------------------
|
|
406
|
+
|
|
407
|
+
const content = (
|
|
408
|
+
<Box style={{ position: 'relative', ...style }} className={className} w={width}>
|
|
409
|
+
{isDark && <style>{darkCss}</style>}
|
|
410
|
+
<Box
|
|
411
|
+
ref={containerRef}
|
|
412
|
+
style={{
|
|
413
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
414
|
+
width: '100%',
|
|
415
|
+
}}
|
|
416
|
+
/>
|
|
417
|
+
</Box>
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
if (label || description) {
|
|
421
|
+
return (
|
|
422
|
+
<Input.Wrapper label={label} description={description}>
|
|
423
|
+
{content}
|
|
424
|
+
</Input.Wrapper>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return content;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
ArchbaseResourceTimeline.displayName = 'ArchbaseResourceTimeline';
|
|
432
|
+
|
|
433
|
+
export { ArchbaseResourceTimeline };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import React, { forwardRef, useMemo } from 'react';
|
|
2
|
+
import { Sparkline, SparklineProps } from '@mantine/charts';
|
|
3
|
+
import { Box, Text, Group, Stack, MantineSize, MantineColor } from '@mantine/core';
|
|
4
|
+
import { IconTrendingUp, IconTrendingDown, IconMinus } from '@tabler/icons-react';
|
|
5
|
+
|
|
6
|
+
export type SparklineTrendDirection = 'up' | 'down' | 'neutral';
|
|
7
|
+
|
|
8
|
+
export interface ArchbaseSparklineProps extends Omit<SparklineProps, 'data' | 'trendColors'> {
|
|
9
|
+
/** Dados do sparkline (array de números) */
|
|
10
|
+
data: number[];
|
|
11
|
+
/** Cor do sparkline */
|
|
12
|
+
color?: MantineColor;
|
|
13
|
+
/** Cor de preenchimento (para área) */
|
|
14
|
+
fillColor?: MantineColor;
|
|
15
|
+
/** Opacidade do preenchimento */
|
|
16
|
+
fillOpacity?: number;
|
|
17
|
+
/** Tipo de curva */
|
|
18
|
+
curveType?: 'linear' | 'natural' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter';
|
|
19
|
+
/** Largura da linha */
|
|
20
|
+
strokeWidth?: number;
|
|
21
|
+
/** Se deve mostrar o indicador de tendência */
|
|
22
|
+
showTrend?: boolean;
|
|
23
|
+
/** Se deve colorir automaticamente baseado na tendência */
|
|
24
|
+
autoTrendColors?: boolean;
|
|
25
|
+
/** Cor para tendência positiva */
|
|
26
|
+
trendUpColor?: MantineColor;
|
|
27
|
+
/** Cor para tendência negativa */
|
|
28
|
+
trendDownColor?: MantineColor;
|
|
29
|
+
/** Cor para tendência neutra */
|
|
30
|
+
trendNeutralColor?: MantineColor;
|
|
31
|
+
/** Se deve mostrar o valor atual */
|
|
32
|
+
showValue?: boolean;
|
|
33
|
+
/** Formato do valor */
|
|
34
|
+
valueFormat?: (value: number) => string;
|
|
35
|
+
/** Label opcional */
|
|
36
|
+
label?: string;
|
|
37
|
+
/** Tamanho do label */
|
|
38
|
+
labelSize?: MantineSize;
|
|
39
|
+
/** Altura do sparkline */
|
|
40
|
+
height?: number;
|
|
41
|
+
/** Largura do sparkline */
|
|
42
|
+
width?: number | string;
|
|
43
|
+
/** Classe CSS */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Estilo */
|
|
46
|
+
style?: React.CSSProperties;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const ArchbaseSparkline = forwardRef<HTMLDivElement, ArchbaseSparklineProps>(
|
|
50
|
+
(
|
|
51
|
+
{
|
|
52
|
+
data,
|
|
53
|
+
color = 'blue',
|
|
54
|
+
fillColor,
|
|
55
|
+
fillOpacity = 0.2,
|
|
56
|
+
curveType = 'natural',
|
|
57
|
+
strokeWidth = 2,
|
|
58
|
+
showTrend = false,
|
|
59
|
+
autoTrendColors = false,
|
|
60
|
+
trendUpColor = 'green',
|
|
61
|
+
trendDownColor = 'red',
|
|
62
|
+
trendNeutralColor = 'gray',
|
|
63
|
+
showValue = false,
|
|
64
|
+
valueFormat,
|
|
65
|
+
label,
|
|
66
|
+
labelSize = 'sm',
|
|
67
|
+
height = 40,
|
|
68
|
+
width,
|
|
69
|
+
className,
|
|
70
|
+
style,
|
|
71
|
+
...rest
|
|
72
|
+
},
|
|
73
|
+
ref
|
|
74
|
+
) => {
|
|
75
|
+
// Calcula a tendência
|
|
76
|
+
const trend = useMemo((): SparklineTrendDirection => {
|
|
77
|
+
if (data.length < 2) return 'neutral';
|
|
78
|
+
const first = data[0];
|
|
79
|
+
const last = data[data.length - 1];
|
|
80
|
+
if (last > first) return 'up';
|
|
81
|
+
if (last < first) return 'down';
|
|
82
|
+
return 'neutral';
|
|
83
|
+
}, [data]);
|
|
84
|
+
|
|
85
|
+
// Calcula a variação percentual
|
|
86
|
+
const changePercent = useMemo(() => {
|
|
87
|
+
if (data.length < 2) return 0;
|
|
88
|
+
const first = data[0];
|
|
89
|
+
const last = data[data.length - 1];
|
|
90
|
+
if (first === 0) return 0;
|
|
91
|
+
return ((last - first) / Math.abs(first)) * 100;
|
|
92
|
+
}, [data]);
|
|
93
|
+
|
|
94
|
+
// Determina a cor baseada na tendência
|
|
95
|
+
const effectiveColor = useMemo(() => {
|
|
96
|
+
if (!autoTrendColors) return color;
|
|
97
|
+
switch (trend) {
|
|
98
|
+
case 'up':
|
|
99
|
+
return trendUpColor;
|
|
100
|
+
case 'down':
|
|
101
|
+
return trendDownColor;
|
|
102
|
+
default:
|
|
103
|
+
return trendNeutralColor;
|
|
104
|
+
}
|
|
105
|
+
}, [color, trend, autoTrendColors, trendDownColor, trendNeutralColor, trendUpColor]);
|
|
106
|
+
|
|
107
|
+
// Formata o valor
|
|
108
|
+
const formattedValue = useMemo(() => {
|
|
109
|
+
if (data.length === 0) return '-';
|
|
110
|
+
const lastValue = data[data.length - 1];
|
|
111
|
+
if (valueFormat) return valueFormat(lastValue);
|
|
112
|
+
return lastValue.toLocaleString('pt-BR');
|
|
113
|
+
}, [data, valueFormat]);
|
|
114
|
+
|
|
115
|
+
// Ícone de tendência
|
|
116
|
+
const TrendIcon = useMemo(() => {
|
|
117
|
+
switch (trend) {
|
|
118
|
+
case 'up':
|
|
119
|
+
return IconTrendingUp;
|
|
120
|
+
case 'down':
|
|
121
|
+
return IconTrendingDown;
|
|
122
|
+
default:
|
|
123
|
+
return IconMinus;
|
|
124
|
+
}
|
|
125
|
+
}, [trend]);
|
|
126
|
+
|
|
127
|
+
const trendColor = useMemo(() => {
|
|
128
|
+
switch (trend) {
|
|
129
|
+
case 'up':
|
|
130
|
+
return `var(--mantine-color-${trendUpColor}-6)`;
|
|
131
|
+
case 'down':
|
|
132
|
+
return `var(--mantine-color-${trendDownColor}-6)`;
|
|
133
|
+
default:
|
|
134
|
+
return `var(--mantine-color-${trendNeutralColor}-6)`;
|
|
135
|
+
}
|
|
136
|
+
}, [trend, trendDownColor, trendNeutralColor, trendUpColor]);
|
|
137
|
+
|
|
138
|
+
if (data.length === 0) {
|
|
139
|
+
return (
|
|
140
|
+
<Box ref={ref} className={className} style={{ ...style, width, height }}>
|
|
141
|
+
<Text size="sm" c="dimmed">
|
|
142
|
+
Sem dados
|
|
143
|
+
</Text>
|
|
144
|
+
</Box>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Stack ref={ref} gap={4} className={className} style={{ ...style, width }}>
|
|
150
|
+
{label && (
|
|
151
|
+
<Text size={labelSize} c="dimmed">
|
|
152
|
+
{label}
|
|
153
|
+
</Text>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
<Group gap="xs" align="flex-end">
|
|
157
|
+
<Box style={{ flex: 1 }}>
|
|
158
|
+
<Sparkline
|
|
159
|
+
data={data}
|
|
160
|
+
color={effectiveColor}
|
|
161
|
+
fillOpacity={fillOpacity}
|
|
162
|
+
curveType={curveType}
|
|
163
|
+
strokeWidth={strokeWidth}
|
|
164
|
+
h={height}
|
|
165
|
+
{...rest}
|
|
166
|
+
/>
|
|
167
|
+
</Box>
|
|
168
|
+
|
|
169
|
+
{(showValue || showTrend) && (
|
|
170
|
+
<Stack gap={0} align="flex-end">
|
|
171
|
+
{showValue && (
|
|
172
|
+
<Text size="sm" fw={600}>
|
|
173
|
+
{formattedValue}
|
|
174
|
+
</Text>
|
|
175
|
+
)}
|
|
176
|
+
{showTrend && (
|
|
177
|
+
<Group gap={2}>
|
|
178
|
+
<TrendIcon size={14} color={trendColor} />
|
|
179
|
+
<Text size="xs" c={trend === 'neutral' ? 'dimmed' : effectiveColor}>
|
|
180
|
+
{changePercent >= 0 ? '+' : ''}
|
|
181
|
+
{changePercent.toFixed(1)}%
|
|
182
|
+
</Text>
|
|
183
|
+
</Group>
|
|
184
|
+
)}
|
|
185
|
+
</Stack>
|
|
186
|
+
)}
|
|
187
|
+
</Group>
|
|
188
|
+
</Stack>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
ArchbaseSparkline.displayName = 'ArchbaseSparkline';
|
package/src/charts/index.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
export {ArchbaseTimeline} from './ArchbaseTimeline'
|
|
2
2
|
export type {ArchbaseTimelineItem, ArchbaseTimelineProps} from './ArchbaseTimeline'
|
|
3
3
|
|
|
4
|
+
export {ArchbaseGantt, ViewMode} from './ArchbaseGantt'
|
|
5
|
+
export type {ArchbaseGanttProps, Task as GanttTask} from './ArchbaseGantt'
|
|
6
|
+
|
|
7
|
+
// Resource Timeline (vis-timeline based)
|
|
8
|
+
export {ArchbaseResourceTimeline} from './ArchbaseResourceTimeline'
|
|
9
|
+
export type {ArchbaseResourceTimelineProps, ArchbaseResourceTimelineViewMode} from './ArchbaseResourceTimeline'
|
|
10
|
+
|
|
4
11
|
// vis-timeline (alternative)
|
|
5
|
-
export * from './vis-timeline'
|
|
12
|
+
export * from './vis-timeline'
|
|
13
|
+
|
|
14
|
+
// Sparkline
|
|
15
|
+
export { ArchbaseSparkline } from './ArchbaseSparkline'
|
|
16
|
+
export type { ArchbaseSparklineProps, SparklineTrendDirection } from './ArchbaseSparkline'
|
|
17
|
+
|
|
18
|
+
// Arc Gauge
|
|
19
|
+
export { ArchbaseArcGauge } from './ArchbaseArcGauge'
|
|
20
|
+
export type { ArchbaseArcGaugeProps, ArchbaseArcGaugeSegment } from './ArchbaseArcGauge'
|
|
21
|
+
|
|
22
|
+
// Linear Gauge
|
|
23
|
+
export { ArchbaseLinearGauge } from './ArchbaseLinearGauge'
|
|
24
|
+
export type { ArchbaseLinearGaugeProps, ArchbaseLinearGaugeZone } from './ArchbaseLinearGauge'
|