@spteck/fluentui-react-charts 1.0.7 → 1.0.9
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/charts/BarChart/BarChart.d.ts +2 -1
- package/dist/charts/ComboChart/ComboChart.d.ts +2 -1
- package/dist/charts/Doughnut/DoughnutChart.d.ts +2 -1
- package/dist/charts/PieChart/PieChart.d.ts +2 -1
- package/dist/charts/areaChart/AreaChart.d.ts +2 -1
- package/dist/charts/barHorizontalChart/BarHotizontalChart.d.ts +2 -1
- package/dist/charts/bubbleChart/BubbleChart.d.ts +2 -1
- package/dist/charts/floatBarChart/FloatBarChart.d.ts +2 -1
- package/dist/charts/index.d.ts +14 -0
- package/dist/charts/lineChart/LineChart.d.ts +2 -1
- package/dist/charts/polarChart/PolarChart.d.ts +2 -1
- package/dist/charts/radarChart/RadarChart.d.ts +2 -1
- package/dist/charts/scatterChart/ScatterChart.d.ts +2 -1
- package/dist/charts/stackedLineChart/StackedLineChart.d.ts +2 -1
- package/dist/charts/steamChart/SteamChart.d.ts +2 -1
- package/dist/components/index.d.ts +0 -14
- package/dist/fluentui-react-charts.cjs.development.js +1086 -1072
- package/dist/fluentui-react-charts.cjs.development.js.map +1 -1
- package/dist/fluentui-react-charts.cjs.production.min.js +1 -1
- package/dist/fluentui-react-charts.cjs.production.min.js.map +1 -1
- package/dist/fluentui-react-charts.esm.js +1074 -1074
- package/dist/fluentui-react-charts.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +5 -5
- package/src/assets/sample1.png +0 -0
- package/src/assets/sample2.png +0 -0
- package/src/assets/sample3.png +0 -0
- package/src/charts/BarChart/BarChart.tsx +0 -227
- package/src/charts/BarChart/README.MD +0 -335
- package/src/charts/BarChart/index.ts +0 -1
- package/src/charts/ComboChart/ComboChart.tsx +0 -209
- package/src/charts/ComboChart/README.MD +0 -347
- package/src/charts/ComboChart/index.ts +0 -1
- package/src/charts/Doughnut/DoughnutChart.tsx +0 -152
- package/src/charts/Doughnut/README.MD +0 -296
- package/src/charts/Doughnut/index.ts +0 -1
- package/src/charts/PieChart/PieChart.tsx +0 -148
- package/src/charts/PieChart/README.MD +0 -315
- package/src/charts/PieChart/index.ts +0 -1
- package/src/charts/areaChart/AreaChart.tsx +0 -195
- package/src/charts/areaChart/README.MD +0 -236
- package/src/charts/areaChart/index.ts +0 -1
- package/src/charts/barHorizontalChart/BarHotizontalChart.tsx +0 -200
- package/src/charts/barHorizontalChart/README.MD +0 -278
- package/src/charts/barHorizontalChart/index.ts +0 -2
- package/src/charts/bubbleChart/BubbleChart.tsx +0 -184
- package/src/charts/bubbleChart/README.MD +0 -275
- package/src/charts/bubbleChart/index.ts +0 -1
- package/src/charts/floatBarChart/FloatBarChart.tsx +0 -178
- package/src/charts/floatBarChart/README.MD +0 -354
- package/src/charts/floatBarChart/index.ts +0 -1
- package/src/charts/lineChart/LineChart.tsx +0 -200
- package/src/charts/lineChart/README.MD +0 -354
- package/src/charts/lineChart/index.ts +0 -1
- package/src/charts/polarChart/PolarChart.tsx +0 -161
- package/src/charts/polarChart/README.MD +0 -336
- package/src/charts/polarChart/index.ts +0 -1
- package/src/charts/radarChart/README.MD +0 -388
- package/src/charts/radarChart/RadarChart.tsx +0 -173
- package/src/charts/radarChart/index.ts +0 -1
- package/src/charts/scatterChart/README.MD +0 -335
- package/src/charts/scatterChart/ScatterChart.tsx +0 -155
- package/src/charts/scatterChart/index.ts +0 -1
- package/src/charts/stackedLineChart/README.MD +0 -396
- package/src/charts/stackedLineChart/StackedLineChart.tsx +0 -188
- package/src/charts/stackedLineChart/index.ts +0 -1
- package/src/charts/steamChart/README.MD +0 -414
- package/src/charts/steamChart/SteamChart.tsx +0 -236
- package/src/charts/steamChart/index.ts +0 -1
- package/src/components/RenderLabel/RenderLabel.tsx +0 -39
- package/src/components/RenderLabel/index.ts +0 -2
- package/src/components/RenderLabel/useRenderLabelStylesStyles.ts +0 -25
- package/src/components/RenderLegend/RenderLegend.tsx +0 -40
- package/src/components/RenderTooltip/RenderTooltip.tsx +0 -111
- package/src/components/buttonMenu/ButtonMenu.tsx +0 -186
- package/src/components/buttonMenu/IButtonMenuOption.ts +0 -9
- package/src/components/buttonMenu/IButtonMenuProps.tsx +0 -40
- package/src/components/dashboard/DashBoard.tsx +0 -314
- package/src/components/dashboard/ExampleDashboardUsage.tsx +0 -114
- package/src/components/dashboard/IDashboardProps.tsx +0 -11
- package/src/components/dashboard/NoDashboards.tsx +0 -26
- package/src/components/dashboard/index.ts +0 -3
- package/src/components/dashboard/selectZoom/SelectZoom.tsx +0 -184
- package/src/components/dashboard/useDashboardStyles.ts +0 -76
- package/src/components/index.ts +0 -17
- package/src/components/legendContainer/LegendContainer.tsx +0 -118
- package/src/components/legendeButton/LegendButton.tsx +0 -57
- package/src/components/renderSliceLegend/RenderSliceLegend.tsx +0 -46
- package/src/components/renderValueLegend/RenderValueLegend.tsx +0 -43
- package/src/components/stack/IStackProps.tsx +0 -94
- package/src/components/stack/Stack.tsx +0 -103
- package/src/components/svgImages/BusinessReportIcon.tsx +0 -218
- package/src/components/themeProvider/ThemeProvider.tsx +0 -48
- package/src/constants/Constants.tsx +0 -23
- package/src/graphGlobalStyles/useGraphGlobalStyles.ts +0 -28
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useChartFactory.tsx +0 -136
- package/src/hooks/useChartUtils.tsx +0 -187
- package/src/hooks/useIndexedDBCache.ts +0 -119
- package/src/hooks/useResponsiveLegend.ts +0 -35
- package/src/index.tsx +0 -5
- package/src/models/ChartDatum.ts +0 -4
- package/src/models/ICardChartContainer.tsx +0 -11
- package/src/models/IChart.ts +0 -50
- package/src/models/index.ts +0 -3
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import { Card, CardHeader, Text, Theme } from '@fluentui/react-components';
|
|
2
|
-
import React, {
|
|
3
|
-
useCallback,
|
|
4
|
-
useEffect,
|
|
5
|
-
useMemo,
|
|
6
|
-
useRef,
|
|
7
|
-
useState,
|
|
8
|
-
} from 'react';
|
|
9
|
-
|
|
10
|
-
import { ICardChartContainer } from '../../models/ICardChartContainer';
|
|
11
|
-
import { IDashboardProps } from './IDashboardProps';
|
|
12
|
-
import { NoDashboards } from './NoDashboards';
|
|
13
|
-
import { SelectZoom } from './selectZoom/SelectZoom';
|
|
14
|
-
import { useChartFactory } from '../../hooks/useChartFactory';
|
|
15
|
-
import { useDashboardStyles } from './useDashboardStyles';
|
|
16
|
-
import { useIndexedDBCache } from '../../hooks/useIndexedDBCache';
|
|
17
|
-
|
|
18
|
-
const MINIMUM_DASHBOARD_WIDTH = 600;
|
|
19
|
-
const MAX_ROWS = 4;
|
|
20
|
-
const DASHBOARD_LAYOUT_CACHE_KEY = 'dashboard-layout-settings';
|
|
21
|
-
const DASHBOARD_ORDER_CACHE_KEY = 'dashboard-card-order';
|
|
22
|
-
const CACHE_EXPIRATION_DAYS = 30;
|
|
23
|
-
|
|
24
|
-
export const Dashboard: React.FC<IDashboardProps> = ({
|
|
25
|
-
cardCharts,
|
|
26
|
-
theme,
|
|
27
|
-
containerWidth,
|
|
28
|
-
containerHeight = '100%',
|
|
29
|
-
maxSpanRows = MAX_ROWS,
|
|
30
|
-
}) => {
|
|
31
|
-
const styles = useDashboardStyles();
|
|
32
|
-
const { getChartComponent } = useChartFactory();
|
|
33
|
-
|
|
34
|
-
// Cache with 30-day expiration for dashboard layout settings
|
|
35
|
-
const { getData, setData } = useIndexedDBCache<Record<string, { spanCols: number; spanRows: number }>>(
|
|
36
|
-
CACHE_EXPIRATION_DAYS * 24 * 60 * 60 * 1000
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Cache for card order
|
|
40
|
-
const {
|
|
41
|
-
getData: getOrderData,
|
|
42
|
-
setData: setOrderData
|
|
43
|
-
} = useIndexedDBCache<string[]>(
|
|
44
|
-
CACHE_EXPIRATION_DAYS * 24 * 60 * 60 * 1000
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const [CardChartContainer, setCardChartContainer] = useState<
|
|
50
|
-
ICardChartContainer[]
|
|
51
|
-
>([]);
|
|
52
|
-
|
|
53
|
-
const [sizes, setSizes] = useState<
|
|
54
|
-
Record<string, { spanCols: number; spanRows: number }>
|
|
55
|
-
>({});
|
|
56
|
-
const dragItem = useRef<number | null>(null);
|
|
57
|
-
const dragOverItem = useRef<number | null>(null);
|
|
58
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
59
|
-
|
|
60
|
-
React.useEffect(() => {
|
|
61
|
-
// Load cached card order and sizes
|
|
62
|
-
const initializeData = async () => {
|
|
63
|
-
try {
|
|
64
|
-
const [cachedSizes, cachedOrder] = await Promise.all([
|
|
65
|
-
getData(DASHBOARD_LAYOUT_CACHE_KEY),
|
|
66
|
-
getOrderData(DASHBOARD_ORDER_CACHE_KEY)
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
|
-
// Restore card order if available, otherwise use original order
|
|
70
|
-
let orderedCards = cardCharts;
|
|
71
|
-
if (cachedOrder && cachedOrder.length > 0) {
|
|
72
|
-
// Reorder cards based on cached order, but handle cases where cards might have been added/removed
|
|
73
|
-
const cardMap = new Map(cardCharts.map(card => [card.id, card]));
|
|
74
|
-
const validCachedOrder = cachedOrder.filter(id => cardMap.has(id));
|
|
75
|
-
const missingCards = cardCharts.filter(card => !cachedOrder.includes(card.id));
|
|
76
|
-
|
|
77
|
-
orderedCards = [
|
|
78
|
-
...validCachedOrder.map(id => cardMap.get(id)!),
|
|
79
|
-
...missingCards
|
|
80
|
-
];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
setCardChartContainer(orderedCards);
|
|
84
|
-
|
|
85
|
-
const initialSizes: Record<
|
|
86
|
-
string,
|
|
87
|
-
{ spanCols: number; spanRows: number }
|
|
88
|
-
> = {};
|
|
89
|
-
|
|
90
|
-
cardCharts.forEach(c => {
|
|
91
|
-
const cachedSize = cachedSizes?.[c.id];
|
|
92
|
-
initialSizes[c.id] = {
|
|
93
|
-
spanCols: cachedSize?.spanCols ?? c.defaultSpan?.spanCols ?? 1,
|
|
94
|
-
spanRows: cachedSize?.spanRows ?? c.defaultSpan?.spanRows ?? 1,
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
setSizes(initialSizes);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
// Fallback to default values if cache fails
|
|
101
|
-
console.warn('Failed to load dashboard data from cache:', error);
|
|
102
|
-
setCardChartContainer(cardCharts);
|
|
103
|
-
const fallbackSizes: Record<
|
|
104
|
-
string,
|
|
105
|
-
{ spanCols: number; spanRows: number }
|
|
106
|
-
> = {};
|
|
107
|
-
cardCharts.forEach(c => {
|
|
108
|
-
fallbackSizes[c.id] = {
|
|
109
|
-
spanCols: c.defaultSpan?.spanCols ?? 1,
|
|
110
|
-
spanRows: c.defaultSpan?.spanRows ?? 1,
|
|
111
|
-
};
|
|
112
|
-
});
|
|
113
|
-
setSizes(fallbackSizes);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
initializeData();
|
|
118
|
-
}, [cardCharts]); // Only depend on cardCharts - getData/getOrderData are stable
|
|
119
|
-
|
|
120
|
-
// Save sizes to cache whenever they change
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
const saveSizesToCache = async () => {
|
|
123
|
-
try {
|
|
124
|
-
await setData(DASHBOARD_LAYOUT_CACHE_KEY, sizes);
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.warn('Failed to save dashboard layout to cache:', error);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Only save if sizes is not empty (avoid saving initial empty state)
|
|
131
|
-
if (Object.keys(sizes).length > 0) {
|
|
132
|
-
saveSizesToCache();
|
|
133
|
-
}
|
|
134
|
-
}, [sizes]); // Only depend on sizes - setData is stable
|
|
135
|
-
|
|
136
|
-
// Save card order to cache whenever it changes
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
const saveOrderToCache = async () => {
|
|
139
|
-
try {
|
|
140
|
-
const cardOrder = CardChartContainer.map(card => card.id);
|
|
141
|
-
await setOrderData(DASHBOARD_ORDER_CACHE_KEY, cardOrder);
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.warn('Failed to save dashboard order to cache:', error);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Only save if CardChartContainer is not empty
|
|
148
|
-
if (CardChartContainer.length > 0) {
|
|
149
|
-
saveOrderToCache();
|
|
150
|
-
}
|
|
151
|
-
}, [CardChartContainer]); // Only depend on CardChartContainer - setOrderData is stable
|
|
152
|
-
|
|
153
|
-
useEffect(() => {
|
|
154
|
-
if (containerWidth <= MINIMUM_DASHBOARD_WIDTH) {
|
|
155
|
-
setSizes(() => {
|
|
156
|
-
const reset: Record<
|
|
157
|
-
string,
|
|
158
|
-
{ spanCols: number; spanRows: number }
|
|
159
|
-
> = {};
|
|
160
|
-
CardChartContainer.forEach(c => {
|
|
161
|
-
reset[c.id] = { spanCols: 1, spanRows: 1 };
|
|
162
|
-
});
|
|
163
|
-
return reset;
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}, [containerWidth, CardChartContainer]);
|
|
167
|
-
|
|
168
|
-
const showZoom = useMemo(() => containerWidth > MINIMUM_DASHBOARD_WIDTH, [
|
|
169
|
-
containerWidth,
|
|
170
|
-
]);
|
|
171
|
-
|
|
172
|
-
const { minCardWidth, gridGap, containerPadding } = useMemo(
|
|
173
|
-
() => ({
|
|
174
|
-
minCardWidth: 350,
|
|
175
|
-
gridGap: 16,
|
|
176
|
-
containerPadding: 20,
|
|
177
|
-
}),
|
|
178
|
-
[]
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const innerWidth = containerWidth - containerPadding * 2;
|
|
182
|
-
const columns = useMemo(
|
|
183
|
-
() => Math.floor((innerWidth + gridGap) / (minCardWidth + gridGap)),
|
|
184
|
-
[innerWidth, gridGap, minCardWidth]
|
|
185
|
-
);
|
|
186
|
-
const maxZoom = Math.max(columns, 1);
|
|
187
|
-
|
|
188
|
-
const handleSort = useCallback(() => {
|
|
189
|
-
if (
|
|
190
|
-
dragItem.current !== null &&
|
|
191
|
-
dragOverItem.current !== null &&
|
|
192
|
-
dragItem.current !== dragOverItem.current
|
|
193
|
-
) {
|
|
194
|
-
const copy = [...CardChartContainer];
|
|
195
|
-
const [moved] = copy.splice(dragItem.current, 1);
|
|
196
|
-
copy.splice(dragOverItem.current, 0, moved);
|
|
197
|
-
setCardChartContainer(copy);
|
|
198
|
-
}
|
|
199
|
-
dragItem.current = null;
|
|
200
|
-
dragOverItem.current = null;
|
|
201
|
-
}, [CardChartContainer]);
|
|
202
|
-
|
|
203
|
-
const handleZoomSelect = useCallback(
|
|
204
|
-
(id: string, span: { spanCols: number; spanRows: number }) => {
|
|
205
|
-
const newSizes = {
|
|
206
|
-
spanCols: Math.min(Math.max(span.spanCols, 1), maxZoom),
|
|
207
|
-
spanRows: Math.min(Math.max(span.spanRows, 1), maxSpanRows),
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
setSizes(prev => ({
|
|
211
|
-
...prev,
|
|
212
|
-
[id]: newSizes,
|
|
213
|
-
}));
|
|
214
|
-
},
|
|
215
|
-
[maxZoom, maxSpanRows]
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
const defaultColsAndRowSpanBasedOnNumberColumns = useMemo(() => {
|
|
219
|
-
return CardChartContainer.reduce((acc, card) => {
|
|
220
|
-
acc[card.id] = {
|
|
221
|
-
spanCols: Math.min(card.defaultSpan?.spanCols ?? 1, columns),
|
|
222
|
-
spanRows: card.defaultSpan?.spanRows ?? 1,
|
|
223
|
-
};
|
|
224
|
-
return acc;
|
|
225
|
-
}, {} as Record<string, { spanCols: number; spanRows: number }>);
|
|
226
|
-
}, [CardChartContainer, columns]);
|
|
227
|
-
|
|
228
|
-
const renderCards = useMemo(() => {
|
|
229
|
-
return CardChartContainer.map((cardContainer, idx) => (
|
|
230
|
-
<div
|
|
231
|
-
className={styles.cardWrapper}
|
|
232
|
-
key={cardContainer.id}
|
|
233
|
-
draggable
|
|
234
|
-
onDragStart={() => (dragItem.current = idx)}
|
|
235
|
-
onDragEnter={() => (dragOverItem.current = idx)}
|
|
236
|
-
onDragOver={e => e.preventDefault()}
|
|
237
|
-
onDragEnd={handleSort}
|
|
238
|
-
style={{
|
|
239
|
-
gridColumnEnd: sizes[cardContainer.id]?.spanCols
|
|
240
|
-
? `span ${sizes[cardContainer.id].spanCols}`
|
|
241
|
-
: `span ${defaultColsAndRowSpanBasedOnNumberColumns[
|
|
242
|
-
cardContainer.id
|
|
243
|
-
]?.spanCols || 1}`,
|
|
244
|
-
gridRowEnd: sizes[cardContainer.id]?.spanRows
|
|
245
|
-
? `span ${sizes[cardContainer.id].spanRows}`
|
|
246
|
-
: `span ${defaultColsAndRowSpanBasedOnNumberColumns[
|
|
247
|
-
cardContainer.id
|
|
248
|
-
]?.spanRows || 1}`,
|
|
249
|
-
}}
|
|
250
|
-
>
|
|
251
|
-
<Card className={styles.cardBody}>
|
|
252
|
-
<CardHeader
|
|
253
|
-
header={
|
|
254
|
-
<Text weight="semibold" size={400}>
|
|
255
|
-
{cardContainer.cardTitle}
|
|
256
|
-
</Text>
|
|
257
|
-
}
|
|
258
|
-
action={
|
|
259
|
-
showZoom ? (
|
|
260
|
-
<SelectZoom
|
|
261
|
-
values={
|
|
262
|
-
sizes[cardContainer.id] ||
|
|
263
|
-
defaultColsAndRowSpanBasedOnNumberColumns[
|
|
264
|
-
cardContainer.id
|
|
265
|
-
] || { spanCols: 1, spanRows: 1 }
|
|
266
|
-
}
|
|
267
|
-
maxCols={maxZoom}
|
|
268
|
-
maxRows={maxSpanRows}
|
|
269
|
-
IsOpen={false}
|
|
270
|
-
onChange={v => handleZoomSelect(cardContainer.id, v)}
|
|
271
|
-
/>
|
|
272
|
-
) : (
|
|
273
|
-
undefined
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
/>
|
|
277
|
-
<div
|
|
278
|
-
className={styles.chartContainer}
|
|
279
|
-
style={{ height: containerHeight }}
|
|
280
|
-
>
|
|
281
|
-
{theme &&
|
|
282
|
-
theme.fontSizeBase100 &&
|
|
283
|
-
getChartComponent(cardContainer.chart, theme as Theme)}
|
|
284
|
-
</div>
|
|
285
|
-
</Card>
|
|
286
|
-
</div>
|
|
287
|
-
));
|
|
288
|
-
}, [
|
|
289
|
-
CardChartContainer,
|
|
290
|
-
handleSort,
|
|
291
|
-
sizes,
|
|
292
|
-
styles.cardBody,
|
|
293
|
-
styles.chartContainer,
|
|
294
|
-
showZoom,
|
|
295
|
-
maxZoom,
|
|
296
|
-
getChartComponent,
|
|
297
|
-
handleZoomSelect,
|
|
298
|
-
theme,
|
|
299
|
-
]);
|
|
300
|
-
|
|
301
|
-
if (CardChartContainer.length === 0) {
|
|
302
|
-
return <NoDashboards />;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
<>
|
|
307
|
-
<div ref={containerRef} className={styles.dashboardContainer} style={{ height: containerHeight }}>
|
|
308
|
-
{renderCards}
|
|
309
|
-
</div>
|
|
310
|
-
</>
|
|
311
|
-
);
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
export default Dashboard;
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { Text, Theme, webLightTheme } from '@fluentui/react-components';
|
|
2
|
-
|
|
3
|
-
import DashBoard from './DashBoard';
|
|
4
|
-
import { ICardChartContainer } from '../../models/ICardChartContainer';
|
|
5
|
-
import React from 'react';
|
|
6
|
-
import { Stack } from '../stack/Stack';
|
|
7
|
-
|
|
8
|
-
// Example usage of the reusable DashBoard component
|
|
9
|
-
const ExampleDashboardUsage: React.FC<{ theme: Theme }> = ({ theme }) => {
|
|
10
|
-
// Sample data for the charts
|
|
11
|
-
const sampleData = [
|
|
12
|
-
{ month: 'Jan', sales: 10000, expenses: 8000 },
|
|
13
|
-
{ month: 'Feb', sales: 15000, expenses: 9000 },
|
|
14
|
-
{ month: 'Mar', sales: 12000, expenses: 7500 },
|
|
15
|
-
{ month: 'Apr', sales: 18000, expenses: 10000 },
|
|
16
|
-
{ month: 'May', sales: 16000, expenses: 8500 },
|
|
17
|
-
{ month: 'Jun', sales: 22000, expenses: 11000 },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
// Define the chart containers with different chart types
|
|
21
|
-
const cardChartContainers: ICardChartContainer[] = [
|
|
22
|
-
{
|
|
23
|
-
id: '1',
|
|
24
|
-
cardTitle: 'Monthly Sales',
|
|
25
|
-
chart: {
|
|
26
|
-
id: '1',
|
|
27
|
-
title: 'Sales Performance',
|
|
28
|
-
type: 'line',
|
|
29
|
-
data: [{
|
|
30
|
-
label: 'Sales',
|
|
31
|
-
data: sampleData.map(d => ({ name: d.month, value: d.sales, x: d.month, y: d.sales }))
|
|
32
|
-
}],
|
|
33
|
-
},
|
|
34
|
-
defaultSpan: { spanCols: 2, spanRows: 1 },
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: '2',
|
|
38
|
-
cardTitle: 'Sales vs Expenses',
|
|
39
|
-
chart: {
|
|
40
|
-
id: '2',
|
|
41
|
-
title: 'Financial Overview',
|
|
42
|
-
type: 'bar',
|
|
43
|
-
data: [
|
|
44
|
-
{
|
|
45
|
-
label: 'Sales',
|
|
46
|
-
data: sampleData.map(d => ({ name: d.month, value: d.sales, x: d.month, y: d.sales }))
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: 'Expenses',
|
|
50
|
-
data: sampleData.map(d => ({ name: d.month, value: d.expenses, x: d.month, y: d.expenses }))
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
defaultSpan: { spanCols: 2, spanRows: 2 },
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: '3',
|
|
58
|
-
cardTitle: 'Revenue Distribution',
|
|
59
|
-
chart: {
|
|
60
|
-
id: '3',
|
|
61
|
-
title: 'Q2 Revenue',
|
|
62
|
-
type: 'pie',
|
|
63
|
-
data: [{
|
|
64
|
-
label: 'Revenue',
|
|
65
|
-
data: [
|
|
66
|
-
{ name: 'Product A', value: 35000 },
|
|
67
|
-
{ name: 'Product B', value: 25000 },
|
|
68
|
-
{ name: 'Product C', value: 18000 },
|
|
69
|
-
{ name: 'Services', value: 15000 },
|
|
70
|
-
]
|
|
71
|
-
}],
|
|
72
|
-
},
|
|
73
|
-
defaultSpan: { spanCols: 1, spanRows: 1 },
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: '4',
|
|
77
|
-
cardTitle: 'Growth Trend',
|
|
78
|
-
chart: {
|
|
79
|
-
id: '4',
|
|
80
|
-
title: 'Monthly Growth',
|
|
81
|
-
type: 'area',
|
|
82
|
-
data: [{
|
|
83
|
-
label: 'Growth',
|
|
84
|
-
data: sampleData.map(d => {
|
|
85
|
-
const growthPercentage = ((d.sales - d.expenses) / d.expenses * 100);
|
|
86
|
-
return {
|
|
87
|
-
name: d.month,
|
|
88
|
-
value: Number(growthPercentage.toFixed(1)),
|
|
89
|
-
x: d.month,
|
|
90
|
-
y: Number(growthPercentage.toFixed(1))
|
|
91
|
-
};
|
|
92
|
-
})
|
|
93
|
-
}],
|
|
94
|
-
},
|
|
95
|
-
defaultSpan: { spanCols: 2, spanRows: 1 },
|
|
96
|
-
},
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<Stack direction="vertical" style={{ width: '100%', height: '100vh' }}>
|
|
101
|
-
<Stack padding="l">
|
|
102
|
-
<Text as="h1" size={900} weight="bold">Sample Dashboard</Text>
|
|
103
|
-
</Stack>
|
|
104
|
-
<DashBoard
|
|
105
|
-
cardCharts={cardChartContainers}
|
|
106
|
-
theme={theme ?? webLightTheme}
|
|
107
|
-
containerWidth={window.innerWidth}
|
|
108
|
-
containerHeight={(window.innerHeight - 100).toString()} // Adjust height for header
|
|
109
|
-
/>
|
|
110
|
-
</Stack>
|
|
111
|
-
);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export default ExampleDashboardUsage;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { ICardChartContainer } from '../../models/ICardChartContainer';
|
|
2
|
-
import { Theme } from '@fluentui/react-components';
|
|
3
|
-
|
|
4
|
-
export interface IDashboardProps {
|
|
5
|
-
cardCharts: ICardChartContainer[];
|
|
6
|
-
theme: Theme;
|
|
7
|
-
containerWidth: number;
|
|
8
|
-
containerHeight?: string;
|
|
9
|
-
|
|
10
|
-
maxSpanRows?: number;
|
|
11
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
import BusinessReportIcon from '../svgImages/BusinessReportIcon';
|
|
4
|
-
import Stack from '../stack/Stack';
|
|
5
|
-
import { Text } from '@fluentui/react-components';
|
|
6
|
-
|
|
7
|
-
export interface INoDashboardsProps {
|
|
8
|
-
height?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const NoDashboards: React.FunctionComponent<INoDashboardsProps> = (props: React.PropsWithChildren<INoDashboardsProps>) => {
|
|
12
|
-
const { height } = props;
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<>
|
|
16
|
-
<Stack
|
|
17
|
-
style={{ height: height || "100%" }}
|
|
18
|
-
justifyContent="Center"
|
|
19
|
-
alignItems="Center"
|
|
20
|
-
>
|
|
21
|
-
<BusinessReportIcon width={200} height={200} />
|
|
22
|
-
<Text size={500} weight='semibold'>No Dashboards Available</Text>
|
|
23
|
-
</Stack>
|
|
24
|
-
</>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Caption1,
|
|
5
|
-
Menu,
|
|
6
|
-
MenuButton,
|
|
7
|
-
MenuPopover,
|
|
8
|
-
MenuProps,
|
|
9
|
-
MenuTrigger,
|
|
10
|
-
Tooltip,
|
|
11
|
-
tokens,
|
|
12
|
-
} from "@fluentui/react-components";
|
|
13
|
-
import {
|
|
14
|
-
Settings20Filled,
|
|
15
|
-
Settings20Regular,
|
|
16
|
-
bundleIcon,
|
|
17
|
-
} from "@fluentui/react-icons";
|
|
18
|
-
|
|
19
|
-
import { RenderLabel } from "../../RenderLabel";
|
|
20
|
-
import { css } from "@emotion/css";
|
|
21
|
-
|
|
22
|
-
export interface ISelectZoomProps {
|
|
23
|
-
IsOpen: boolean;
|
|
24
|
-
onChange?: (value: { spanCols: number; spanRows: number }) => void;
|
|
25
|
-
values: { spanCols: number; spanRows: number };
|
|
26
|
-
maxCols: number;
|
|
27
|
-
maxRows: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const useStyles = (): {
|
|
31
|
-
gridContainer: string;
|
|
32
|
-
cell: string;
|
|
33
|
-
hoveredCell: string;
|
|
34
|
-
selectedCell: string;
|
|
35
|
-
menuPopover: string;
|
|
36
|
-
bottomText: string;
|
|
37
|
-
zoomContainer: string;
|
|
38
|
-
} => ({
|
|
39
|
-
gridContainer: css`
|
|
40
|
-
display: grid;
|
|
41
|
-
gap: 4px;
|
|
42
|
-
padding: 8px;
|
|
43
|
-
justify-content: center;
|
|
44
|
-
`,
|
|
45
|
-
cell: css`
|
|
46
|
-
width: 30px;
|
|
47
|
-
height: 30px;
|
|
48
|
-
border: 1px solid ${tokens.colorNeutralStroke1};
|
|
49
|
-
background-color: ${tokens.colorNeutralBackground1};
|
|
50
|
-
cursor: pointer;
|
|
51
|
-
transition: background-color 150ms ease, transform 150ms ease;
|
|
52
|
-
will-change: background-color, transform;
|
|
53
|
-
`,
|
|
54
|
-
hoveredCell: css`
|
|
55
|
-
background-color: ${tokens.colorNeutralBackground1Hover};
|
|
56
|
-
transform: scale(1.05);
|
|
57
|
-
`,
|
|
58
|
-
selectedCell: css`
|
|
59
|
-
background-color: ${tokens.colorNeutralBackground1Selected};
|
|
60
|
-
`,
|
|
61
|
-
menuPopover: css`
|
|
62
|
-
min-width: fit-content;
|
|
63
|
-
`,
|
|
64
|
-
bottomText: css`
|
|
65
|
-
padding-left: 8px;
|
|
66
|
-
padding-right: 8px;
|
|
67
|
-
`,
|
|
68
|
-
zoomContainer: css({
|
|
69
|
-
display: 'flex',
|
|
70
|
-
flexDirection: 'row',
|
|
71
|
-
alignItems: 'center',
|
|
72
|
-
gap: '6px',
|
|
73
|
-
width: '100%',
|
|
74
|
-
boxSizing: 'border-box',
|
|
75
|
-
|
|
76
|
-
padding: '8px 0px',
|
|
77
|
-
|
|
78
|
-
}),
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export const SelectZoom: React.FunctionComponent<ISelectZoomProps> = (
|
|
82
|
-
props: React.PropsWithChildren<ISelectZoomProps>
|
|
83
|
-
) => {
|
|
84
|
-
const { onChange, values: defaultValues, maxCols, maxRows } = props;
|
|
85
|
-
const Settings = bundleIcon(Settings20Filled, Settings20Regular);
|
|
86
|
-
const styles = useStyles();
|
|
87
|
-
|
|
88
|
-
const [values, setValues] = React.useState(defaultValues);
|
|
89
|
-
const [hovered, setHovered] = React.useState<{
|
|
90
|
-
spanCols: number;
|
|
91
|
-
spanRows: number;
|
|
92
|
-
} | null>(null);
|
|
93
|
-
const [open, setOpen] = React.useState(false);
|
|
94
|
-
|
|
95
|
-
React.useEffect(() => {
|
|
96
|
-
setValues(defaultValues);
|
|
97
|
-
}, [defaultValues]);
|
|
98
|
-
|
|
99
|
-
const onOpenChange: MenuProps["onOpenChange"] = (_, data) => {
|
|
100
|
-
setOpen(data.open);
|
|
101
|
-
setHovered(null);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const handleCellClick = (row: number, col: number):void => {
|
|
105
|
-
const newValues = { spanCols: col + 1, spanRows: row + 1 };
|
|
106
|
-
setValues(newValues);
|
|
107
|
-
onChange?.(newValues);
|
|
108
|
-
setOpen(false);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const handleCellHover = (row: number, col: number):void => {
|
|
112
|
-
setHovered({ spanCols: col + 1, spanRows: row + 1 });
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const handleMouseLeave = ():void => {
|
|
116
|
-
setHovered(null);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const renderGridCells = (): React.ReactNode[] => {
|
|
120
|
-
const cells: React.ReactNode[] = [];
|
|
121
|
-
for (let row = 0; row < maxRows; row++) {
|
|
122
|
-
for (let col = 0; col < maxCols; col++) {
|
|
123
|
-
const isSelected = row < values.spanRows && col < values.spanCols;
|
|
124
|
-
const isHovered =
|
|
125
|
-
hovered && row < hovered.spanRows && col < hovered.spanCols;
|
|
126
|
-
cells.push(
|
|
127
|
-
<div
|
|
128
|
-
key={`${row}-${col}`}
|
|
129
|
-
className={`${styles.cell} ${isHovered ? styles.hoveredCell : ""} ${
|
|
130
|
-
isSelected ? styles.selectedCell : ""
|
|
131
|
-
}`}
|
|
132
|
-
onMouseEnter={() => handleCellHover(row, col)}
|
|
133
|
-
onClick={() => handleCellClick(row, col)}
|
|
134
|
-
/>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return cells;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Compute popover width dynamically
|
|
142
|
-
const popoverWidth = React.useMemo(() => (30 * maxCols) + (4 * (maxCols - 1)) + 32, [maxCols]); // 30px for each cell, 4px gap, and 32px padding (16 left and right)
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<Menu open={open} onOpenChange={onOpenChange}>
|
|
146
|
-
<MenuTrigger disableButtonEnhancement>
|
|
147
|
-
<Tooltip content="Configure" relationship="label">
|
|
148
|
-
<MenuButton
|
|
149
|
-
icon={<Settings />}
|
|
150
|
-
size="small"
|
|
151
|
-
appearance="transparent"
|
|
152
|
-
/>
|
|
153
|
-
</Tooltip>
|
|
154
|
-
</MenuTrigger>
|
|
155
|
-
|
|
156
|
-
<MenuPopover
|
|
157
|
-
style={{ width: `${popoverWidth}px`, minWidth: "120px", padding: 8 }}
|
|
158
|
-
>
|
|
159
|
-
<div
|
|
160
|
-
className={styles.zoomContainer}
|
|
161
|
-
>
|
|
162
|
-
<RenderLabel
|
|
163
|
-
label={`Span (${values.spanCols} × ${values.spanRows})`}
|
|
164
|
-
|
|
165
|
-
/>
|
|
166
|
-
</div>
|
|
167
|
-
|
|
168
|
-
{/* Grid */}
|
|
169
|
-
<div
|
|
170
|
-
className={styles.gridContainer}
|
|
171
|
-
style={{
|
|
172
|
-
gridTemplateColumns: `repeat(${maxCols}, 30px)`,
|
|
173
|
-
gridTemplateRows: `repeat(${maxRows}, 30px)`,
|
|
174
|
-
}}
|
|
175
|
-
onMouseLeave={handleMouseLeave}
|
|
176
|
-
>
|
|
177
|
-
{renderGridCells()}
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
<Caption1 className={styles.bottomText}>Click to set span</Caption1>
|
|
181
|
-
</MenuPopover>
|
|
182
|
-
</Menu>
|
|
183
|
-
);
|
|
184
|
-
};
|