@juspay/blend-design-system 0.0.37-beta.3 → 0.0.37-beta.4
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/components/AccordionV2/index.d.ts +3 -0
- package/dist/components/AvatarV2/avatarV2.utils.d.ts +1 -1
- package/dist/components/AvatarV2/index.d.ts +1 -2
- package/dist/components/BreadcrumbV2/index.d.ts +10 -0
- package/dist/components/ButtonV2/ButtonGroupV2/index.d.ts +1 -0
- package/dist/components/ButtonV2/buttonV2.types.d.ts +0 -4
- package/dist/components/ButtonV2/index.d.ts +3 -0
- package/dist/components/ButtonV2/utils.d.ts +1 -1
- package/dist/components/ChartsV2/index.d.ts +5 -0
- package/dist/components/CodeEditorV2/CodeEditorV2.d.ts +1 -1
- package/dist/components/CodeEditorV2/codeEditorV2.tokens.d.ts +5 -5
- package/dist/components/CodeEditorV2/codeEditorV2.types.d.ts +5 -5
- package/dist/components/CodeEditorV2/index.d.ts +2 -0
- package/dist/components/CodeEditorV2/utils.d.ts +1 -1
- package/dist/components/DataTable/DataTable.d.ts +2 -1
- package/dist/components/DataTable/PivotTableModal/PivotPreviewPanel.d.ts +3 -0
- package/dist/components/DataTable/PivotTableModal/PivotTableIllustration.d.ts +7 -0
- package/dist/components/DataTable/PivotTableModal/index.d.ts +3 -0
- package/dist/components/DataTable/PivotTableModal/pivotModalStyleTokens.d.ts +123 -0
- package/dist/components/DataTable/PivotTableModal/types.d.ts +62 -0
- package/dist/components/DataTable/PivotTableModal/utils.d.ts +32 -0
- package/dist/components/DataTable/TableBody/types.d.ts +2 -0
- package/dist/components/DataTable/TableHeader/types.d.ts +1 -0
- package/dist/components/DataTable/index.d.ts +2 -0
- package/dist/components/DataTable/types.d.ts +56 -0
- package/dist/components/DataTable/utils.d.ts +19 -1
- package/dist/components/InputsV2/ChatInputV2/AttachmentDropdown.d.ts +3 -3
- package/dist/components/InputsV2/ChatInputV2/ChatInputTagV2.d.ts +2 -2
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.d.ts +4 -4
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2.types.d.ts +8 -8
- package/dist/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.d.ts +3 -3
- package/dist/components/InputsV2/ChatInputV2/MobileChatInputV2.d.ts +2 -2
- package/dist/components/InputsV2/ChatInputV2/utils.d.ts +4 -4
- package/dist/components/InputsV2/SearchInputV2/utils.d.ts +39 -0
- package/dist/components/InputsV2/TextInputV2/TextInputV2.types.d.ts +2 -2
- package/dist/components/InputsV2/TextInputV2/index.d.ts +2 -0
- package/dist/components/InputsV2/utils/utils.d.ts +1 -1
- package/dist/components/KeyValuePairV2/KeyValuePairV2.d.ts +1 -1
- package/dist/components/KeyValuePairV2/ResponsiveText.d.ts +2 -2
- package/dist/components/KeyValuePairV2/index.d.ts +3 -0
- package/dist/components/KeyValuePairV2/keyValuePairV2.types.d.ts +2 -2
- package/dist/components/KeyValuePairV2/responsiveTextStyles.d.ts +3 -3
- package/dist/components/KeyValuePairV2/utils.d.ts +2 -2
- package/dist/components/MenuV2/index.d.ts +1 -0
- package/dist/components/MenuV2/menuV2.utils.d.ts +2 -2
- package/dist/components/MultiSelectV2/index.d.ts +3 -0
- package/dist/components/MultiSelectV2/multiSelectV2.types.d.ts +1 -1
- package/dist/components/MultiSelectV2/utils.d.ts +2 -2
- package/dist/components/ProgressBarV2/index.d.ts +3 -0
- package/dist/components/ProgressBarV2/utils.d.ts +1 -1
- package/dist/components/SelectV2/index.d.ts +1 -0
- package/dist/components/SelectorV2/CheckboxV2/index.d.ts +4 -0
- package/dist/components/SelectorV2/CheckboxV2/utils.d.ts +1 -1
- package/dist/components/SelectorV2/RadioV2/index.d.ts +3 -0
- package/dist/components/SelectorV2/SwitchV2/index.d.ts +1 -0
- package/dist/components/SidebarV2/index.d.ts +5 -0
- package/dist/components/SingleSelectV2/SingleSelectV2VirtualList.d.ts +2 -2
- package/dist/components/SingleSelectV2/index.d.ts +3 -0
- package/dist/components/SingleSelectV2/singleSelectV2.types.d.ts +2 -2
- package/dist/components/SingleSelectV2/utils.d.ts +6 -6
- package/dist/components/StatCardV2/index.d.ts +10 -1
- package/dist/components/StepperV2/index.d.ts +3 -1
- package/dist/components/StepperV2/stepperV2.types.d.ts +2 -2
- package/dist/components/TabsV2/index.d.ts +3 -1
- package/dist/components/TagV2/index.d.ts +3 -0
- package/dist/components/TooltipV2/index.d.ts +1 -0
- package/dist/components/common/index.d.ts +1 -1
- package/dist/main.d.ts +30 -70
- package/dist/main.js +87817 -85412
- package/dist/{node-CRWdZOVN.js → node-C2uf3sNA.js} +1303 -1300
- package/dist/node.js +1 -1
- package/dist/tokens.js +1 -1
- package/lib/components/AccordionV2/index.ts +3 -0
- package/lib/components/AvatarV2/AvatarV2.tsx +2 -2
- package/lib/components/AvatarV2/avatarV2.utils.ts +1 -1
- package/lib/components/AvatarV2/index.ts +1 -12
- package/lib/components/BreadcrumbV2/index.ts +10 -0
- package/lib/components/ButtonV2/ButtonGroupV2/index.ts +1 -0
- package/lib/components/ButtonV2/ButtonV2.tsx +2 -2
- package/lib/components/ButtonV2/LinkButton.tsx +2 -2
- package/lib/components/ButtonV2/buttonV2.types.ts +0 -6
- package/lib/components/ButtonV2/index.ts +3 -0
- package/lib/components/ButtonV2/utils.ts +2 -2
- package/lib/components/Charts/BlendChart.tsx +1 -1
- package/lib/components/ChartsV2/ChartV2.tsx +3 -2
- package/lib/components/ChartsV2/index.ts +5 -0
- package/lib/components/CodeEditorV2/CodeEditorV2.tsx +2 -2
- package/lib/components/CodeEditorV2/codeEditorV2.dark.tokens.ts +37 -25
- package/lib/components/CodeEditorV2/codeEditorV2.light.token.ts +37 -25
- package/lib/components/CodeEditorV2/codeEditorV2.tokens.ts +5 -5
- package/lib/components/CodeEditorV2/codeEditorV2.types.ts +5 -5
- package/lib/components/CodeEditorV2/index.ts +2 -0
- package/lib/components/CodeEditorV2/utils.ts +1 -1
- package/lib/components/DataTable/DataTable.tsx +148 -4
- package/lib/components/DataTable/PivotTableModal/PivotPreviewPanel.tsx +174 -0
- package/lib/components/DataTable/PivotTableModal/PivotTableIllustration.tsx +28 -0
- package/lib/components/DataTable/PivotTableModal/index.tsx +859 -0
- package/lib/components/DataTable/PivotTableModal/pivot-table-illustration.png +0 -0
- package/lib/components/DataTable/PivotTableModal/pivotModal.styled.ts +13 -0
- package/lib/components/DataTable/PivotTableModal/pivotModalStyleTokens.ts +250 -0
- package/lib/components/DataTable/PivotTableModal/types.ts +69 -0
- package/lib/components/DataTable/PivotTableModal/utils.ts +360 -0
- package/lib/components/DataTable/TableBody/index.tsx +16 -5
- package/lib/components/DataTable/TableBody/types.ts +2 -0
- package/lib/components/DataTable/TableHeader/index.tsx +6 -3
- package/lib/components/DataTable/TableHeader/types.ts +1 -0
- package/lib/components/DataTable/index.ts +4 -0
- package/lib/components/DataTable/types.ts +57 -0
- package/lib/components/DataTable/utils.ts +197 -0
- package/lib/components/InputsV2/ChatInputV2/AttachmentDropdown.tsx +3 -3
- package/lib/components/InputsV2/ChatInputV2/ChatInputTagV2.tsx +3 -3
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2.types.ts +8 -8
- package/lib/components/InputsV2/ChatInputV2/ChatInputV2AttachmentRow.tsx +7 -7
- package/lib/components/InputsV2/ChatInputV2/utils.ts +8 -8
- package/lib/components/InputsV2/SearchInputV2/utils.ts +14 -1
- package/lib/components/InputsV2/TextInputV2/TextInputV2.tsx +3 -3
- package/lib/components/InputsV2/TextInputV2/TextInputV2.types.ts +2 -2
- package/lib/components/InputsV2/TextInputV2/index.ts +2 -0
- package/lib/components/KeyValuePairV2/KeyValuePairV2.tsx +6 -2
- package/lib/components/KeyValuePairV2/ResponsiveText.tsx +2 -2
- package/lib/components/KeyValuePairV2/index.ts +3 -0
- package/lib/components/KeyValuePairV2/keyValuePairV2.types.ts +2 -2
- package/lib/components/KeyValuePairV2/responsiveTextStyles.ts +3 -3
- package/lib/components/KeyValuePairV2/utils.ts +3 -3
- package/lib/components/MenuV2/MenuV2.tsx +2 -2
- package/lib/components/MenuV2/MenuV2SubMenu.tsx +2 -2
- package/lib/components/MenuV2/index.ts +1 -0
- package/lib/components/MenuV2/menuV2.utils.ts +4 -4
- package/lib/components/MultiSelectV2/MultiSelectV2.tsx +2 -2
- package/lib/components/MultiSelectV2/MultiSelectV2Menu.tsx +5 -2
- package/lib/components/MultiSelectV2/index.ts +3 -0
- package/lib/components/MultiSelectV2/mobile/MobileMultiSelectV2.tsx +7 -4
- package/lib/components/MultiSelectV2/multiSelectV2.types.ts +1 -1
- package/lib/components/MultiSelectV2/utils.ts +2 -2
- package/lib/components/ProgressBarV2/ProgressBarV2.tsx +5 -2
- package/lib/components/ProgressBarV2/index.ts +3 -0
- package/lib/components/ProgressBarV2/utils.ts +1 -1
- package/lib/components/SelectV2/index.ts +1 -0
- package/lib/components/SelectorV2/CheckboxV2/CheckboxV2.tsx +2 -2
- package/lib/components/SelectorV2/CheckboxV2/index.ts +4 -0
- package/lib/components/SelectorV2/CheckboxV2/utils.ts +1 -1
- package/lib/components/SelectorV2/RadioV2/index.ts +3 -0
- package/lib/components/SelectorV2/SwitchV2/index.ts +1 -0
- package/lib/components/Sidebar/Sidebar.tsx +7 -2
- package/lib/components/SidebarV2/index.ts +5 -0
- package/lib/components/SingleSelectV2/MobileSingleSelectV2.tsx +2 -2
- package/lib/components/SingleSelectV2/SingleSelectV2.tsx +10 -3
- package/lib/components/SingleSelectV2/SingleSelectV2Menu.tsx +4 -2
- package/lib/components/SingleSelectV2/SingleSelectV2VirtualList.tsx +5 -2
- package/lib/components/SingleSelectV2/index.ts +7 -0
- package/lib/components/SingleSelectV2/singleSelectV2.types.ts +2 -2
- package/lib/components/SingleSelectV2/utils.ts +10 -10
- package/lib/components/StatCardV2/index.ts +13 -1
- package/lib/components/StepperV2/index.ts +3 -1
- package/lib/components/StepperV2/stepperV2.types.ts +2 -2
- package/lib/components/TabsV2/index.ts +13 -1
- package/lib/components/TagV2/index.ts +3 -0
- package/lib/components/TooltipV2/index.ts +1 -0
- package/lib/components/common/index.ts +1 -1
- package/lib/main.ts +34 -258
- package/lib/types/assets.d.ts +24 -0
- package/package.json +2 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import Block from '../../Primitives/Block/Block'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scroll region without visible scrollbars (preview + config panels).
|
|
6
|
+
*/
|
|
7
|
+
export const NoScrollbar = styled(Block)`
|
|
8
|
+
scrollbar-width: none;
|
|
9
|
+
-ms-overflow-style: none;
|
|
10
|
+
&::-webkit-scrollbar {
|
|
11
|
+
display: none;
|
|
12
|
+
}
|
|
13
|
+
`
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { FoundationTokenType } from '../../../tokens/theme.token'
|
|
2
|
+
import type { TableTokenType } from '../dataTable.tokens'
|
|
3
|
+
|
|
4
|
+
export type PivotModalStyleTokens = {
|
|
5
|
+
modal: {
|
|
6
|
+
minWidth: string
|
|
7
|
+
maxWidth: string
|
|
8
|
+
maxHeight: string
|
|
9
|
+
bodyPadding: string
|
|
10
|
+
bodyGap: string
|
|
11
|
+
}
|
|
12
|
+
rightPanel: {
|
|
13
|
+
width: string
|
|
14
|
+
padding: string
|
|
15
|
+
background: string
|
|
16
|
+
border: string
|
|
17
|
+
borderRadius: string
|
|
18
|
+
}
|
|
19
|
+
shell: {
|
|
20
|
+
display: 'grid'
|
|
21
|
+
gridTemplateColumns: string
|
|
22
|
+
height: string
|
|
23
|
+
overflow: 'hidden'
|
|
24
|
+
}
|
|
25
|
+
layout: {
|
|
26
|
+
wideDesktopMinWidth: number
|
|
27
|
+
wideDesktopColumns: string
|
|
28
|
+
fallbackColumns: string
|
|
29
|
+
}
|
|
30
|
+
panelPadding: string
|
|
31
|
+
configPanelBackground: string
|
|
32
|
+
previewPanelBackground: string
|
|
33
|
+
text: {
|
|
34
|
+
sectionTitle: {
|
|
35
|
+
fontSize: string
|
|
36
|
+
fontWeight: number
|
|
37
|
+
color: string
|
|
38
|
+
}
|
|
39
|
+
fieldLabel: {
|
|
40
|
+
fontSize: string
|
|
41
|
+
fontWeight: number
|
|
42
|
+
color: string
|
|
43
|
+
}
|
|
44
|
+
checkboxLabel: {
|
|
45
|
+
fontSize: string
|
|
46
|
+
color: string
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
spacing: {
|
|
50
|
+
sectionGap: string
|
|
51
|
+
builderIntroGap: string
|
|
52
|
+
stackGap: string
|
|
53
|
+
controlsRowGap: string
|
|
54
|
+
iconBadgeGap: string
|
|
55
|
+
sectionHeaderMarginBottom: string
|
|
56
|
+
}
|
|
57
|
+
dropZone: {
|
|
58
|
+
emptyMinHeight: string
|
|
59
|
+
padding: string
|
|
60
|
+
borderRadius: string
|
|
61
|
+
border: string
|
|
62
|
+
background: string
|
|
63
|
+
}
|
|
64
|
+
chip: {
|
|
65
|
+
borderRadius: string
|
|
66
|
+
padding: string
|
|
67
|
+
hoverShadow: string
|
|
68
|
+
hoverTranslateY: string
|
|
69
|
+
transition: string
|
|
70
|
+
}
|
|
71
|
+
iconBadge: {
|
|
72
|
+
size: string
|
|
73
|
+
borderRadius: string
|
|
74
|
+
glyphFontSize: string | number
|
|
75
|
+
glyphFontWeight: number
|
|
76
|
+
}
|
|
77
|
+
sectionLabel: {
|
|
78
|
+
fontWeight: number
|
|
79
|
+
fontSize: string | number
|
|
80
|
+
textTransform: 'uppercase'
|
|
81
|
+
letterSpacing: string
|
|
82
|
+
}
|
|
83
|
+
sectionCount: {
|
|
84
|
+
fontSize: string | number
|
|
85
|
+
color: string
|
|
86
|
+
}
|
|
87
|
+
emptyState: {
|
|
88
|
+
titleFontSize: string | number
|
|
89
|
+
titleColor: string
|
|
90
|
+
exampleFontSize: string | number
|
|
91
|
+
exampleColor: string
|
|
92
|
+
padding: string
|
|
93
|
+
titleMarginBottom: string
|
|
94
|
+
}
|
|
95
|
+
fieldRowLabel: {
|
|
96
|
+
fontSize: string | number
|
|
97
|
+
color: string
|
|
98
|
+
marginBottom: string
|
|
99
|
+
}
|
|
100
|
+
removeButton: {
|
|
101
|
+
minWidth: string
|
|
102
|
+
padding: string
|
|
103
|
+
borderRadius: string
|
|
104
|
+
border: string
|
|
105
|
+
background: string
|
|
106
|
+
hoverBackground: string
|
|
107
|
+
iconColor: string
|
|
108
|
+
iconSize: number
|
|
109
|
+
}
|
|
110
|
+
menuCheckIconSize: number
|
|
111
|
+
preview: {
|
|
112
|
+
headerMarginBottom: string
|
|
113
|
+
titleFontWeight: number
|
|
114
|
+
titleFontSize: string | number
|
|
115
|
+
titleMarginBottom: string
|
|
116
|
+
metaFontSize: string
|
|
117
|
+
}
|
|
118
|
+
bodyLineHeight: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Layout and typography for the pivot modal, derived from foundation tokens + responsive table tokens.
|
|
123
|
+
* Section chips use `colors.primary` only (see color.tokens).
|
|
124
|
+
*/
|
|
125
|
+
export const getPivotModalStyleTokens = (
|
|
126
|
+
f: FoundationTokenType,
|
|
127
|
+
tableToken: TableTokenType
|
|
128
|
+
): PivotModalStyleTokens => {
|
|
129
|
+
const { dataTable, header } = tableToken
|
|
130
|
+
const cell = dataTable.table.body.cell
|
|
131
|
+
|
|
132
|
+
const rightPanelMinWidth = '360px'
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
modal: {
|
|
136
|
+
minWidth: 'min(95vw, 120rem)',
|
|
137
|
+
maxWidth: '95vw',
|
|
138
|
+
maxHeight: '92vh',
|
|
139
|
+
bodyPadding: f.unit[20],
|
|
140
|
+
bodyGap: f.unit[16],
|
|
141
|
+
},
|
|
142
|
+
rightPanel: {
|
|
143
|
+
width: rightPanelMinWidth,
|
|
144
|
+
padding: f.unit[20],
|
|
145
|
+
background: f.colors.gray[0] as string,
|
|
146
|
+
border: `${f.border.width[1]} solid ${f.colors.gray[200]}`,
|
|
147
|
+
borderRadius: f.border.radius[8],
|
|
148
|
+
},
|
|
149
|
+
shell: {
|
|
150
|
+
display: 'grid',
|
|
151
|
+
gridTemplateColumns: `minmax(0, 1fr) ${rightPanelMinWidth}`,
|
|
152
|
+
height: `min(${dataTable.maxHeight}, 85vh)`,
|
|
153
|
+
overflow: 'hidden',
|
|
154
|
+
},
|
|
155
|
+
layout: {
|
|
156
|
+
wideDesktopMinWidth: 1800,
|
|
157
|
+
wideDesktopColumns: `minmax(0, 4fr) minmax(${rightPanelMinWidth}, 1fr)`,
|
|
158
|
+
fallbackColumns: `minmax(0, 1fr) ${rightPanelMinWidth}`,
|
|
159
|
+
},
|
|
160
|
+
panelPadding: f.unit[16],
|
|
161
|
+
configPanelBackground: f.colors.gray[50] as string,
|
|
162
|
+
previewPanelBackground: dataTable.table.body.backgroundColor as string,
|
|
163
|
+
text: {
|
|
164
|
+
sectionTitle: {
|
|
165
|
+
fontSize: f.font.size.body.md.fontSize,
|
|
166
|
+
fontWeight: f.font.weight[600],
|
|
167
|
+
color: f.colors.gray[800] as string,
|
|
168
|
+
},
|
|
169
|
+
fieldLabel: {
|
|
170
|
+
fontSize: f.font.size.body.md.fontSize,
|
|
171
|
+
fontWeight: f.font.weight[500],
|
|
172
|
+
color: f.colors.gray[800] as string,
|
|
173
|
+
},
|
|
174
|
+
checkboxLabel: {
|
|
175
|
+
fontSize: f.font.size.body.sm.fontSize,
|
|
176
|
+
color: f.colors.gray[600] as string,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
spacing: {
|
|
180
|
+
sectionGap: f.unit[20],
|
|
181
|
+
builderIntroGap: f.unit[20],
|
|
182
|
+
stackGap: f.unit[8],
|
|
183
|
+
controlsRowGap: f.unit[8],
|
|
184
|
+
iconBadgeGap: f.unit[8],
|
|
185
|
+
sectionHeaderMarginBottom: f.unit[12],
|
|
186
|
+
},
|
|
187
|
+
dropZone: {
|
|
188
|
+
emptyMinHeight: f.unit[80],
|
|
189
|
+
padding: f.unit[12],
|
|
190
|
+
borderRadius: f.border.radius[8],
|
|
191
|
+
border: `2px dashed ${f.colors.gray[200]}`,
|
|
192
|
+
background: f.colors.gray[0] as string,
|
|
193
|
+
},
|
|
194
|
+
chip: {
|
|
195
|
+
borderRadius: f.border.radius[6],
|
|
196
|
+
padding: `${f.unit[8]} ${f.unit[12]}`,
|
|
197
|
+
hoverShadow: f.shadows.sm,
|
|
198
|
+
hoverTranslateY: `-${f.unit[1]}`,
|
|
199
|
+
transition: 'box-shadow 0.2s ease, transform 0.2s ease',
|
|
200
|
+
},
|
|
201
|
+
iconBadge: {
|
|
202
|
+
size: f.unit[24],
|
|
203
|
+
borderRadius: f.border.radius[4],
|
|
204
|
+
glyphFontSize: f.font.size.body.sm.fontSize,
|
|
205
|
+
glyphFontWeight: 700,
|
|
206
|
+
},
|
|
207
|
+
sectionLabel: {
|
|
208
|
+
fontWeight: 600,
|
|
209
|
+
fontSize: f.font.size.body.sm.fontSize,
|
|
210
|
+
textTransform: 'uppercase',
|
|
211
|
+
letterSpacing: f.unit[0.5],
|
|
212
|
+
},
|
|
213
|
+
sectionCount: {
|
|
214
|
+
fontSize: f.font.size.body.sm.fontSize,
|
|
215
|
+
color: f.colors.gray[600] as string,
|
|
216
|
+
},
|
|
217
|
+
emptyState: {
|
|
218
|
+
titleFontSize: f.font.size.body.md.fontSize,
|
|
219
|
+
titleColor: f.colors.gray[800] as string,
|
|
220
|
+
exampleFontSize: f.font.size.body.sm.fontSize,
|
|
221
|
+
exampleColor: f.colors.gray[400] as string,
|
|
222
|
+
padding: f.unit[16],
|
|
223
|
+
titleMarginBottom: f.unit[8],
|
|
224
|
+
},
|
|
225
|
+
fieldRowLabel: {
|
|
226
|
+
fontSize: f.font.size.body.sm.fontSize,
|
|
227
|
+
color: f.colors.gray[600] as string,
|
|
228
|
+
marginBottom: f.unit[8],
|
|
229
|
+
},
|
|
230
|
+
removeButton: {
|
|
231
|
+
minWidth: f.unit[28],
|
|
232
|
+
padding: f.unit[4],
|
|
233
|
+
borderRadius: f.border.radius[4],
|
|
234
|
+
border: `${f.border.width[1]} solid ${f.colors.gray[300]}`,
|
|
235
|
+
background: f.colors.gray[0] as string,
|
|
236
|
+
hoverBackground: f.colors.gray[100] as string,
|
|
237
|
+
iconColor: f.colors.gray[600] as string,
|
|
238
|
+
iconSize: Number.parseInt(String(f.unit[14]), 10) || 14,
|
|
239
|
+
},
|
|
240
|
+
menuCheckIconSize: Number.parseInt(String(f.unit[16]), 10) || 16,
|
|
241
|
+
preview: {
|
|
242
|
+
headerMarginBottom: f.unit[16],
|
|
243
|
+
titleFontWeight: 600,
|
|
244
|
+
titleFontSize: f.font.size.body.md.fontSize,
|
|
245
|
+
titleMarginBottom: f.unit[4],
|
|
246
|
+
metaFontSize: cell.fontSize as string,
|
|
247
|
+
},
|
|
248
|
+
bodyLineHeight: header.description.lineHeight as string,
|
|
249
|
+
} as PivotModalStyleTokens
|
|
250
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import type { TableTokenType } from '../dataTable.tokens'
|
|
3
|
+
import { ColumnDefinition, PivotAggregationType } from '../types'
|
|
4
|
+
import type { PivotModalStyleTokens } from './pivotModalStyleTokens'
|
|
5
|
+
|
|
6
|
+
export type PivotValueConfig<T extends Record<string, unknown>> = {
|
|
7
|
+
field: keyof T
|
|
8
|
+
aggregation: PivotAggregationType
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PivotTableConfig<T extends Record<string, unknown>> = {
|
|
12
|
+
rows: Array<keyof T>
|
|
13
|
+
columns: Array<keyof T>
|
|
14
|
+
values: PivotValueConfig<T>[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type PivotTableModalProps<T extends Record<string, unknown>> = {
|
|
18
|
+
isOpen: boolean
|
|
19
|
+
onClose: () => void
|
|
20
|
+
data: T[]
|
|
21
|
+
columns: ColumnDefinition<T>[]
|
|
22
|
+
title?: string
|
|
23
|
+
description?: string
|
|
24
|
+
showExport?: boolean
|
|
25
|
+
initialConfig?: Partial<PivotTableConfig<T>>
|
|
26
|
+
previewColumns?: PivotPreviewColumn[]
|
|
27
|
+
previewRows?: PivotPreviewRow[]
|
|
28
|
+
onConfigChange?: (config: PivotTableConfig<T>) => void
|
|
29
|
+
onExport?: (config: PivotTableConfig<T>) => void
|
|
30
|
+
trigger?: ReactNode
|
|
31
|
+
/**
|
|
32
|
+
* Callback when trigger is clicked. Use this to open the modal.
|
|
33
|
+
*/
|
|
34
|
+
onTriggerClick?: () => void
|
|
35
|
+
/**
|
|
36
|
+
* Configure which aggregation operations are available in the Values section.
|
|
37
|
+
* If not provided, all operations will be shown.
|
|
38
|
+
* @example ['sum', 'count', 'average'] // Show only sum, count, and average
|
|
39
|
+
*/
|
|
40
|
+
availableAggregations?: PivotAggregationType[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type PivotPreviewColumn = {
|
|
44
|
+
key: string
|
|
45
|
+
label: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PivotPreviewRow = Record<string, unknown> & {
|
|
49
|
+
__pivotId: string
|
|
50
|
+
__pivotRowType?: 'data' | 'subtotal' | 'grand_total'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type PivotPreviewPanelProps = {
|
|
54
|
+
pivot: PivotModalStyleTokens
|
|
55
|
+
tableToken: TableTokenType
|
|
56
|
+
showExport: boolean
|
|
57
|
+
previewRows?: PivotPreviewRow[]
|
|
58
|
+
previewColumns?: PivotPreviewColumn[]
|
|
59
|
+
previewTableColumns: ColumnDefinition<Record<string, unknown>>[]
|
|
60
|
+
onExport: () => void
|
|
61
|
+
hasValues: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type PivotFieldConfig = {
|
|
65
|
+
field: string
|
|
66
|
+
showTotal: boolean
|
|
67
|
+
aggregation?: PivotAggregationType
|
|
68
|
+
header?: string
|
|
69
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { ColumnDefinition, PivotAggregationType } from '../types'
|
|
2
|
+
import { PivotPreviewColumn, PivotPreviewRow, PivotValueConfig } from './types'
|
|
3
|
+
|
|
4
|
+
export const normalizePivotValue = (value: unknown): string => {
|
|
5
|
+
if (value == null) return 'N/A'
|
|
6
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
7
|
+
return String(value)
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
return value.map((item) => normalizePivotValue(item)).join(', ')
|
|
11
|
+
}
|
|
12
|
+
if (typeof value === 'object') {
|
|
13
|
+
const objectValue = value as Record<string, unknown>
|
|
14
|
+
if ('text' in objectValue) return String(objectValue.text)
|
|
15
|
+
if ('label' in objectValue) return String(objectValue.label)
|
|
16
|
+
if ('value' in objectValue) return String(objectValue.value)
|
|
17
|
+
if ('selectedValue' in objectValue)
|
|
18
|
+
return String(objectValue.selectedValue)
|
|
19
|
+
if ('values' in objectValue && Array.isArray(objectValue.values)) {
|
|
20
|
+
return objectValue.values
|
|
21
|
+
.map((item) => normalizePivotValue(item))
|
|
22
|
+
.join(', ')
|
|
23
|
+
}
|
|
24
|
+
if ('name' in objectValue) return String(objectValue.name)
|
|
25
|
+
try {
|
|
26
|
+
return JSON.stringify(objectValue)
|
|
27
|
+
} catch {
|
|
28
|
+
return 'N/A'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return String(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const truncatePivotLabel = (value: string, maxLength = 36): string => {
|
|
35
|
+
if (value.length <= maxLength) return value
|
|
36
|
+
return `${value.slice(0, maxLength - 3)}...`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const toNumeric = (value: unknown): number => {
|
|
40
|
+
if (typeof value === 'number') return value
|
|
41
|
+
const parsed = Number(
|
|
42
|
+
normalizePivotValue(value)
|
|
43
|
+
.replace(/,/g, '')
|
|
44
|
+
.replace(/[^\d.-]/g, '')
|
|
45
|
+
)
|
|
46
|
+
return Number.isNaN(parsed) ? 0 : parsed
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const aggregate = (
|
|
50
|
+
rows: Array<Record<string, unknown>>,
|
|
51
|
+
field: string,
|
|
52
|
+
aggregation: PivotAggregationType
|
|
53
|
+
): number => {
|
|
54
|
+
if (aggregation === PivotAggregationType.COUNT) {
|
|
55
|
+
return rows.length
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const values = rows.map((row) => toNumeric(row[field]))
|
|
59
|
+
if (!values.length) return 0
|
|
60
|
+
|
|
61
|
+
switch (aggregation) {
|
|
62
|
+
case PivotAggregationType.SUM:
|
|
63
|
+
return values.reduce((sum, value) => sum + value, 0)
|
|
64
|
+
case PivotAggregationType.AVERAGE:
|
|
65
|
+
case PivotAggregationType.MEAN:
|
|
66
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length
|
|
67
|
+
case PivotAggregationType.MEDIAN: {
|
|
68
|
+
const sorted = [...values].sort((a, b) => a - b)
|
|
69
|
+
const middle = Math.floor(sorted.length / 2)
|
|
70
|
+
return sorted.length % 2 === 0
|
|
71
|
+
? (sorted[middle - 1] + sorted[middle]) / 2
|
|
72
|
+
: sorted[middle]
|
|
73
|
+
}
|
|
74
|
+
case PivotAggregationType.MIN:
|
|
75
|
+
return Math.min(...values)
|
|
76
|
+
case PivotAggregationType.MAX:
|
|
77
|
+
return Math.max(...values)
|
|
78
|
+
default:
|
|
79
|
+
return values.reduce((sum, value) => sum + value, 0)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const getPivotFieldOptions = <T extends Record<string, unknown>>(
|
|
84
|
+
columns: ColumnDefinition<T>[]
|
|
85
|
+
): Array<{ key: string; label: string }> =>
|
|
86
|
+
columns.map((column) => ({
|
|
87
|
+
key: String(column.field),
|
|
88
|
+
label: column.header || String(column.field),
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
export const isPivotNumericValue = (value: unknown): boolean => {
|
|
92
|
+
if (typeof value === 'number') return Number.isFinite(value)
|
|
93
|
+
if (value == null) return false
|
|
94
|
+
const normalized = normalizePivotValue(value)
|
|
95
|
+
.replace(/,/g, '')
|
|
96
|
+
.replace(/[^\d.-]/g, '')
|
|
97
|
+
.trim()
|
|
98
|
+
if (!normalized) return false
|
|
99
|
+
const parsed = Number(normalized)
|
|
100
|
+
return Number.isFinite(parsed)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const getSupportedAggregationsForField = <
|
|
104
|
+
T extends Record<string, unknown>,
|
|
105
|
+
>(
|
|
106
|
+
data: T[],
|
|
107
|
+
field: keyof T,
|
|
108
|
+
allowedAggregations: PivotAggregationType[],
|
|
109
|
+
isNumericColumn = false
|
|
110
|
+
): PivotAggregationType[] => {
|
|
111
|
+
const hasNumericValue =
|
|
112
|
+
isNumericColumn || data.some((row) => isPivotNumericValue(row[field]))
|
|
113
|
+
|
|
114
|
+
return allowedAggregations.filter((aggregation) => {
|
|
115
|
+
if (aggregation === PivotAggregationType.COUNT) return true
|
|
116
|
+
return hasNumericValue
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Builds the rectangular grid the modal preview renders as a {@link DataTable}.
|
|
122
|
+
*
|
|
123
|
+
* - **Columns:** First column key `__rowLabel` — header is joined row field names
|
|
124
|
+
* (or `All Rows`). Each unique combination of **column** dimension values
|
|
125
|
+
* becomes a group; for each group, every **value** config adds one column
|
|
126
|
+
* (`columnKey__field__aggregation`).
|
|
127
|
+
* - **Rows:** One row per distinct row-dimension key; cells are aggregated from
|
|
128
|
+
* raw rows that match that row key and column key.
|
|
129
|
+
* - **Grand total:** Appended last with `__pivotRowType: 'grand_total'` and
|
|
130
|
+
* `__rowLabel` `"Grand Total"`; each metric is aggregated over the full
|
|
131
|
+
* (already filtered) `data` for that column bucket.
|
|
132
|
+
*/
|
|
133
|
+
export const buildPivotPreview = <T extends Record<string, unknown>>(
|
|
134
|
+
data: T[],
|
|
135
|
+
rowFields: Array<keyof T> | Array<{ field: keyof T; showTotal?: boolean }>,
|
|
136
|
+
columnFields:
|
|
137
|
+
| Array<keyof T>
|
|
138
|
+
| Array<{ field: keyof T; showTotal?: boolean }>,
|
|
139
|
+
valueConfigs: PivotValueConfig<T>[],
|
|
140
|
+
fieldLabelByKey: Record<string, string> = {}
|
|
141
|
+
): { columns: PivotPreviewColumn[]; rows: PivotPreviewRow[] } => {
|
|
142
|
+
if (!valueConfigs.length) return { columns: [], rows: [] }
|
|
143
|
+
|
|
144
|
+
const rowFieldConfigs = rowFields.map((field) =>
|
|
145
|
+
typeof field === 'object' && field !== null
|
|
146
|
+
? { field: field.field, showTotal: field.showTotal === true }
|
|
147
|
+
: { field, showTotal: false }
|
|
148
|
+
) as Array<{ field: keyof T; showTotal: boolean }>
|
|
149
|
+
const columnFieldConfigs = columnFields.map((field) =>
|
|
150
|
+
typeof field === 'object' && field !== null
|
|
151
|
+
? { field: field.field, showTotal: field.showTotal === true }
|
|
152
|
+
: { field, showTotal: false }
|
|
153
|
+
) as Array<{ field: keyof T; showTotal: boolean }>
|
|
154
|
+
|
|
155
|
+
const normalizedRowFields = rowFields.map((field) =>
|
|
156
|
+
typeof field === 'object' && field !== null ? field.field : field
|
|
157
|
+
) as Array<keyof T>
|
|
158
|
+
const normalizedColumnFields = columnFields.map((field) =>
|
|
159
|
+
typeof field === 'object' && field !== null ? field.field : field
|
|
160
|
+
) as Array<keyof T>
|
|
161
|
+
|
|
162
|
+
const shouldShowRowGrandTotal =
|
|
163
|
+
normalizedRowFields.length === 0 ||
|
|
164
|
+
rowFieldConfigs.some((r) => r.showTotal)
|
|
165
|
+
const shouldShowColumnTotals =
|
|
166
|
+
normalizedColumnFields.length > 0 &&
|
|
167
|
+
columnFieldConfigs.some((c) => c.showTotal)
|
|
168
|
+
|
|
169
|
+
type PivotRowNode = {
|
|
170
|
+
rows: T[]
|
|
171
|
+
children: Map<string, PivotRowNode>
|
|
172
|
+
}
|
|
173
|
+
const rowTree: PivotRowNode = { rows: [], children: new Map() }
|
|
174
|
+
const columnKeySet = new Set<string>()
|
|
175
|
+
|
|
176
|
+
data.forEach((row) => {
|
|
177
|
+
let node = rowTree
|
|
178
|
+
node.rows.push(row)
|
|
179
|
+
normalizedRowFields.forEach((field) => {
|
|
180
|
+
const part = normalizePivotValue(row[field])
|
|
181
|
+
const next = node.children.get(part)
|
|
182
|
+
if (next) {
|
|
183
|
+
node = next
|
|
184
|
+
node.rows.push(row)
|
|
185
|
+
} else {
|
|
186
|
+
const created: PivotRowNode = {
|
|
187
|
+
rows: [row],
|
|
188
|
+
children: new Map(),
|
|
189
|
+
}
|
|
190
|
+
node.children.set(part, created)
|
|
191
|
+
node = created
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const columnKey = normalizedColumnFields.length
|
|
196
|
+
? normalizedColumnFields
|
|
197
|
+
.map((field) => normalizePivotValue(row[field]))
|
|
198
|
+
.join(' | ')
|
|
199
|
+
: 'All Columns'
|
|
200
|
+
|
|
201
|
+
columnKeySet.add(columnKey)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const orderedColumnKeys = Array.from(columnKeySet).sort((a, b) =>
|
|
205
|
+
a.localeCompare(b)
|
|
206
|
+
)
|
|
207
|
+
const getFieldLabel = (key: keyof T): string =>
|
|
208
|
+
fieldLabelByKey[String(key)] || String(key)
|
|
209
|
+
|
|
210
|
+
const rowLabel = normalizedRowFields.length
|
|
211
|
+
? normalizedRowFields.map((field) => getFieldLabel(field)).join(' / ')
|
|
212
|
+
: 'All Rows'
|
|
213
|
+
|
|
214
|
+
const previewColumns: PivotPreviewColumn[] = [
|
|
215
|
+
{ key: '__rowLabel', label: rowLabel },
|
|
216
|
+
...orderedColumnKeys.flatMap((columnKey) =>
|
|
217
|
+
valueConfigs.map((valueConfig, valueIndex) => ({
|
|
218
|
+
key: `${columnKey}__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`,
|
|
219
|
+
label: `${truncatePivotLabel(columnKey)} | ${valueConfig.aggregation}(${getFieldLabel(valueConfig.field)})`,
|
|
220
|
+
}))
|
|
221
|
+
),
|
|
222
|
+
...(shouldShowColumnTotals
|
|
223
|
+
? valueConfigs.map((valueConfig, valueIndex) => ({
|
|
224
|
+
key: `__columnTotal__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`,
|
|
225
|
+
label: `Total | ${valueConfig.aggregation}(${getFieldLabel(valueConfig.field)})`,
|
|
226
|
+
}))
|
|
227
|
+
: []),
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
let idCounter = 0
|
|
231
|
+
const computeMetricCells = (
|
|
232
|
+
targetRow: PivotPreviewRow,
|
|
233
|
+
groupedRows: T[]
|
|
234
|
+
) => {
|
|
235
|
+
orderedColumnKeys.forEach((columnKey) => {
|
|
236
|
+
const columnRows = normalizedColumnFields.length
|
|
237
|
+
? groupedRows.filter(
|
|
238
|
+
(groupRow) =>
|
|
239
|
+
normalizedColumnFields
|
|
240
|
+
.map((field) =>
|
|
241
|
+
normalizePivotValue(groupRow[field])
|
|
242
|
+
)
|
|
243
|
+
.join(' | ') === columnKey
|
|
244
|
+
)
|
|
245
|
+
: groupedRows
|
|
246
|
+
|
|
247
|
+
valueConfigs.forEach((valueConfig, valueIndex) => {
|
|
248
|
+
const key = `${columnKey}__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
|
|
249
|
+
targetRow[key] = Number(
|
|
250
|
+
aggregate(
|
|
251
|
+
columnRows as Array<Record<string, unknown>>,
|
|
252
|
+
String(valueConfig.field),
|
|
253
|
+
valueConfig.aggregation
|
|
254
|
+
).toFixed(2)
|
|
255
|
+
)
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
if (shouldShowColumnTotals) {
|
|
260
|
+
valueConfigs.forEach((valueConfig, valueIndex) => {
|
|
261
|
+
const key = `__columnTotal__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
|
|
262
|
+
targetRow[key] = Number(
|
|
263
|
+
aggregate(
|
|
264
|
+
groupedRows as Array<Record<string, unknown>>,
|
|
265
|
+
String(valueConfig.field),
|
|
266
|
+
valueConfig.aggregation
|
|
267
|
+
).toFixed(2)
|
|
268
|
+
)
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const rows: PivotPreviewRow[] = []
|
|
274
|
+
const traverse = (node: PivotRowNode, depth: number, parts: string[]) => {
|
|
275
|
+
if (depth === normalizedRowFields.length) {
|
|
276
|
+
const rowKey = parts.length ? parts.join(' | ') : 'All Rows'
|
|
277
|
+
const row: PivotPreviewRow = {
|
|
278
|
+
__pivotId: `pivot-row-${idCounter++}`,
|
|
279
|
+
__rowLabel: truncatePivotLabel(rowKey, 60),
|
|
280
|
+
__pivotRowType: 'data',
|
|
281
|
+
}
|
|
282
|
+
computeMetricCells(row, node.rows)
|
|
283
|
+
rows.push(row)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const childKeys = Array.from(node.children.keys()).sort((a, b) =>
|
|
288
|
+
a.localeCompare(b)
|
|
289
|
+
)
|
|
290
|
+
childKeys.forEach((key) => {
|
|
291
|
+
const child = node.children.get(key)
|
|
292
|
+
if (!child) return
|
|
293
|
+
traverse(child, depth + 1, [...parts, key])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
if (
|
|
297
|
+
normalizedRowFields.length > 1 &&
|
|
298
|
+
depth < normalizedRowFields.length - 1 &&
|
|
299
|
+
rowFieldConfigs[depth]?.showTotal
|
|
300
|
+
) {
|
|
301
|
+
const label = `${parts.join(' / ')} Total`
|
|
302
|
+
const subtotalRow: PivotPreviewRow = {
|
|
303
|
+
__pivotId: `pivot-subtotal-${idCounter++}`,
|
|
304
|
+
__rowLabel: truncatePivotLabel(label, 60),
|
|
305
|
+
__pivotRowType: 'subtotal',
|
|
306
|
+
}
|
|
307
|
+
computeMetricCells(subtotalRow, node.rows)
|
|
308
|
+
rows.push(subtotalRow)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
traverse(rowTree, 0, [])
|
|
313
|
+
|
|
314
|
+
if (shouldShowRowGrandTotal) {
|
|
315
|
+
const grandTotalRow: PivotPreviewRow = {
|
|
316
|
+
__pivotId: 'pivot-grand-total',
|
|
317
|
+
__rowLabel: 'Grand Total',
|
|
318
|
+
__pivotRowType: 'grand_total',
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
orderedColumnKeys.forEach((columnKey) => {
|
|
322
|
+
const columnRows = normalizedColumnFields.length
|
|
323
|
+
? data.filter(
|
|
324
|
+
(row) =>
|
|
325
|
+
normalizedColumnFields
|
|
326
|
+
.map((field) => normalizePivotValue(row[field]))
|
|
327
|
+
.join(' | ') === columnKey
|
|
328
|
+
)
|
|
329
|
+
: data
|
|
330
|
+
valueConfigs.forEach((valueConfig, valueIndex) => {
|
|
331
|
+
const key = `${columnKey}__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
|
|
332
|
+
grandTotalRow[key] = Number(
|
|
333
|
+
aggregate(
|
|
334
|
+
columnRows as Array<Record<string, unknown>>,
|
|
335
|
+
String(valueConfig.field),
|
|
336
|
+
valueConfig.aggregation
|
|
337
|
+
).toFixed(2)
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
if (shouldShowColumnTotals) {
|
|
342
|
+
valueConfigs.forEach((valueConfig, valueIndex) => {
|
|
343
|
+
const key = `__columnTotal__${String(valueConfig.field)}__${valueConfig.aggregation}__valueIndex_${valueIndex}`
|
|
344
|
+
grandTotalRow[key] = Number(
|
|
345
|
+
aggregate(
|
|
346
|
+
data as Array<Record<string, unknown>>,
|
|
347
|
+
String(valueConfig.field),
|
|
348
|
+
valueConfig.aggregation
|
|
349
|
+
).toFixed(2)
|
|
350
|
+
)
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
rows.push(grandTotalRow)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
columns: previewColumns,
|
|
358
|
+
rows,
|
|
359
|
+
}
|
|
360
|
+
}
|