@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.
Files changed (168) hide show
  1. package/dist/buttons/ArchbaseSpeedDial.d.ts +59 -0
  2. package/dist/buttons/index.d.ts +2 -0
  3. package/dist/charts/ArchbaseArcGauge.d.ts +55 -0
  4. package/dist/charts/ArchbaseGantt.d.ts +70 -0
  5. package/dist/charts/ArchbaseLinearGauge.d.ts +55 -0
  6. package/dist/charts/ArchbaseResourceTimeline.d.ts +61 -0
  7. package/dist/charts/ArchbaseSparkline.d.ts +45 -0
  8. package/dist/charts/index.d.ts +10 -0
  9. package/dist/containers/ArchbaseExpansionPanel.d.ts +60 -0
  10. package/dist/containers/ArchbaseFloatingPanel.d.ts +91 -0
  11. package/dist/containers/index.d.ts +4 -0
  12. package/dist/datagrid/export/ArchbaseDataGridExcelExport.d.ts +67 -0
  13. package/dist/datagrid/export/index.d.ts +2 -0
  14. package/dist/datagrid/index.d.ts +1 -0
  15. package/dist/display/ArchbaseAnimationWrapper.d.ts +73 -0
  16. package/dist/display/ArchbaseBarcodeGenerator.d.ts +50 -0
  17. package/dist/display/ArchbaseCarousel.d.ts +100 -0
  18. package/dist/display/ArchbaseCodeViewer.d.ts +63 -0
  19. package/dist/display/ArchbaseDiffViewer.d.ts +41 -0
  20. package/dist/display/ArchbaseJsonTree.d.ts +34 -0
  21. package/dist/display/ArchbaseLightbox.d.ts +89 -0
  22. package/dist/display/ArchbaseMarquee.d.ts +46 -0
  23. package/dist/display/ArchbasePhotoAlbum.d.ts +58 -0
  24. package/dist/display/ArchbaseQRCode.d.ts +44 -0
  25. package/dist/display/ArchbaseTooltipRich.d.ts +82 -0
  26. package/dist/display/ArchbaseVirtualList.d.ts +116 -0
  27. package/dist/display/index.d.ts +24 -0
  28. package/dist/editors/ArchbaseAIPromptInput.d.ts +69 -0
  29. package/dist/editors/ArchbaseAsyncMultiSelect.d.ts +1 -1
  30. package/dist/editors/ArchbaseAsyncSelect.d.ts +1 -1
  31. package/dist/editors/ArchbaseBarcodeScanner.d.ts +63 -0
  32. package/dist/editors/ArchbaseCepInput.d.ts +26 -0
  33. package/dist/editors/ArchbaseColorGradientPicker.d.ts +80 -0
  34. package/dist/editors/ArchbaseCpfCnpjInput.d.ts +21 -0
  35. package/dist/editors/ArchbaseCurrencyInput.d.ts +25 -0
  36. package/dist/editors/ArchbaseDualListbox.d.ts +61 -0
  37. package/dist/editors/ArchbaseInPlaceEditor.d.ts +67 -0
  38. package/dist/editors/ArchbaseLightGrid.d.ts +56 -0
  39. package/dist/editors/ArchbaseMentionInput.d.ts +75 -0
  40. package/dist/editors/ArchbaseMultiEmail.d.ts +53 -0
  41. package/dist/editors/ArchbaseMultiViewCalendar.d.ts +47 -0
  42. package/dist/editors/ArchbaseNumberStepper.d.ts +66 -0
  43. package/dist/editors/ArchbaseOTPInput.d.ts +27 -0
  44. package/dist/editors/ArchbasePhoneInput.d.ts +52 -0
  45. package/dist/editors/ArchbaseRangeSlider.d.ts +22 -0
  46. package/dist/editors/ArchbaseSignaturePad.d.ts +60 -0
  47. package/dist/editors/ArchbaseSpeechToTextInput.d.ts +106 -0
  48. package/dist/editors/ArchbaseSpreadsheet.d.ts +56 -0
  49. package/dist/editors/ArchbaseTagInput.d.ts +55 -0
  50. package/dist/editors/index.d.ts +40 -0
  51. package/dist/feedback/ArchbaseChunkProgressBar.d.ts +35 -0
  52. package/dist/feedback/ArchbaseRipple.d.ts +43 -0
  53. package/dist/feedback/ArchbaseSkeleton.d.ts +115 -0
  54. package/dist/feedback/index.d.ts +6 -0
  55. package/dist/filters/ArchbaseColumnSelector.d.ts +38 -0
  56. package/dist/filters/index.d.ts +2 -0
  57. package/dist/forms/ArchbaseFieldArray.d.ts +80 -0
  58. package/dist/forms/ArchbaseFormWizard.d.ts +73 -0
  59. package/dist/forms/ArchbaseStepper.d.ts +51 -0
  60. package/dist/forms/index.d.ts +8 -0
  61. package/dist/forms/useArchbaseFormWizard.d.ts +74 -0
  62. package/dist/hooks/index.d.ts +2 -0
  63. package/dist/hooks/useArchbaseConfirm.d.ts +59 -0
  64. package/dist/image/editor/index.d.ts +43 -4
  65. package/dist/image/index.d.ts +1 -0
  66. package/dist/index.css +1 -1
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +22510 -13820
  69. package/dist/list/ArchbaseTreeList.d.ts +76 -0
  70. package/dist/list/index.d.ts +2 -0
  71. package/dist/navigation/ArchbaseActionSheet.d.ts +70 -0
  72. package/dist/navigation/ArchbaseAppBar.d.ts +63 -0
  73. package/dist/navigation/ArchbaseBottomNavigation.d.ts +61 -0
  74. package/dist/navigation/ArchbaseContextMenu.d.ts +76 -0
  75. package/dist/navigation/ArchbaseKeyboardShortcuts.d.ts +85 -0
  76. package/dist/navigation/ArchbaseSpotlight.d.ts +69 -0
  77. package/dist/navigation/index.d.ts +12 -0
  78. package/dist/notification/ArchbaseNotificationCenter.d.ts +56 -0
  79. package/dist/notification/index.d.ts +2 -0
  80. package/dist/onboarding/ArchbaseOnboardingTour.d.ts +27 -76
  81. package/dist/viewers/ArchbaseDocViewer.d.ts +40 -0
  82. package/dist/viewers/ArchbasePdfBuilder.d.ts +53 -0
  83. package/dist/viewers/ArchbaseXmlViewer.d.ts +36 -0
  84. package/dist/viewers/index.d.ts +6 -0
  85. package/package.json +41 -18
  86. package/src/buttons/ArchbaseSpeedDial.tsx +282 -0
  87. package/src/buttons/index.ts +3 -0
  88. package/src/charts/ArchbaseArcGauge.tsx +325 -0
  89. package/src/charts/ArchbaseGantt.tsx +494 -0
  90. package/src/charts/ArchbaseLinearGauge.tsx +288 -0
  91. package/src/charts/ArchbaseResourceTimeline.tsx +433 -0
  92. package/src/charts/ArchbaseSparkline.tsx +193 -0
  93. package/src/charts/index.ts +20 -1
  94. package/src/containers/ArchbaseExpansionPanel.tsx +208 -0
  95. package/src/containers/ArchbaseFloatingPanel.tsx +448 -0
  96. package/src/containers/index.ts +17 -0
  97. package/src/datagrid/components/pagination/grid-pagination.tsx +2 -2
  98. package/src/datagrid/export/ArchbaseDataGridExcelExport.tsx +293 -0
  99. package/src/datagrid/export/index.ts +8 -0
  100. package/src/datagrid/index.tsx +2 -0
  101. package/src/display/ArchbaseAnimationWrapper.tsx +272 -0
  102. package/src/display/ArchbaseBarcodeGenerator.tsx +303 -0
  103. package/src/display/ArchbaseCarousel.tsx +679 -0
  104. package/src/display/ArchbaseCodeViewer.tsx +271 -0
  105. package/src/display/ArchbaseDiffViewer.tsx +108 -0
  106. package/src/display/ArchbaseJsonTree.tsx +150 -0
  107. package/src/display/ArchbaseLightbox.tsx +279 -0
  108. package/src/display/ArchbaseMarquee.tsx +268 -0
  109. package/src/display/ArchbasePhotoAlbum.tsx +415 -0
  110. package/src/display/ArchbaseQRCode.tsx +137 -0
  111. package/src/display/ArchbaseTooltipRich.tsx +317 -0
  112. package/src/display/ArchbaseVirtualList.tsx +618 -0
  113. package/src/display/index.ts +94 -1
  114. package/src/editors/ArchbaseAIPromptInput.tsx +432 -0
  115. package/src/editors/ArchbaseBarcodeScanner.tsx +570 -0
  116. package/src/editors/ArchbaseCepInput.tsx +173 -0
  117. package/src/editors/ArchbaseColorGradientPicker.tsx +460 -0
  118. package/src/editors/ArchbaseCpfCnpjInput.tsx +193 -0
  119. package/src/editors/ArchbaseCurrencyInput.tsx +100 -0
  120. package/src/editors/ArchbaseDualListbox.tsx +671 -0
  121. package/src/editors/ArchbaseInPlaceEditor.tsx +406 -0
  122. package/src/editors/ArchbaseLightGrid.tsx +538 -0
  123. package/src/editors/ArchbaseMentionInput.tsx +384 -0
  124. package/src/editors/ArchbaseMultiEmail.tsx +367 -0
  125. package/src/editors/ArchbaseMultiViewCalendar.tsx +439 -0
  126. package/src/editors/ArchbaseNumberStepper.tsx +219 -0
  127. package/src/editors/ArchbaseOTPInput.tsx +133 -0
  128. package/src/editors/ArchbaseOperationHoursEditor.tsx +1 -1
  129. package/src/editors/ArchbasePhoneInput.tsx +355 -0
  130. package/src/editors/ArchbaseRangeSlider.tsx +109 -0
  131. package/src/editors/ArchbaseSignaturePad.tsx +425 -0
  132. package/src/editors/ArchbaseSpeechToTextInput.tsx +444 -0
  133. package/src/editors/ArchbaseSpreadsheet.tsx +388 -0
  134. package/src/editors/ArchbaseTagInput.tsx +326 -0
  135. package/src/editors/index.tsx +92 -0
  136. package/src/feedback/ArchbaseChunkProgressBar.tsx +156 -0
  137. package/src/feedback/ArchbaseRipple.tsx +278 -0
  138. package/src/feedback/ArchbaseSkeleton.tsx +419 -0
  139. package/src/feedback/index.ts +22 -1
  140. package/src/filters/ArchbaseColumnSelector.tsx +268 -0
  141. package/src/filters/index.ts +2 -0
  142. package/src/forms/ArchbaseFieldArray.tsx +305 -0
  143. package/src/forms/ArchbaseFormWizard.tsx +284 -0
  144. package/src/forms/ArchbaseStepper.tsx +137 -0
  145. package/src/forms/index.ts +24 -2
  146. package/src/forms/useArchbaseFormWizard.ts +283 -0
  147. package/src/hooks/index.ts +12 -0
  148. package/src/hooks/useArchbaseConfirm.ts +293 -0
  149. package/src/image/editor/functions/image-processing.ts +5 -0
  150. package/src/image/editor/index.tsx +305 -391
  151. package/src/image/index.ts +1 -0
  152. package/src/index.ts +3 -0
  153. package/src/list/ArchbaseTreeList.tsx +422 -0
  154. package/src/list/index.ts +3 -0
  155. package/src/navigation/ArchbaseActionSheet.tsx +301 -0
  156. package/src/navigation/ArchbaseAppBar.tsx +233 -0
  157. package/src/navigation/ArchbaseBottomNavigation.tsx +243 -0
  158. package/src/navigation/ArchbaseContextMenu.tsx +298 -0
  159. package/src/navigation/ArchbaseKeyboardShortcuts.tsx +472 -0
  160. package/src/navigation/ArchbaseSpotlight.tsx +483 -0
  161. package/src/navigation/index.ts +48 -1
  162. package/src/notification/ArchbaseNotificationCenter.tsx +502 -0
  163. package/src/notification/index.ts +2 -0
  164. package/src/onboarding/ArchbaseOnboardingTour.tsx +229 -102
  165. package/src/viewers/ArchbaseDocViewer.tsx +584 -0
  166. package/src/viewers/ArchbasePdfBuilder.tsx +291 -0
  167. package/src/viewers/ArchbaseXmlViewer.tsx +142 -0
  168. 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';
@@ -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'