@graphenedata/cli 0.0.12 → 0.0.14
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/cli/cli.js +8591 -1214
- package/dist/docs/base.md +98 -0
- package/dist/docs/cli.md +22 -0
- package/dist/docs/graphene.md +10 -10
- package/dist/ui/component-utilities/echarts.js +2 -3
- package/dist/ui/component-utilities/formatting.js +3 -11
- package/dist/ui/component-utilities/getSeriesConfig.js +2 -1
- package/dist/ui/components/Area.svelte +188 -151
- package/dist/ui/components/AreaChart.svelte +43 -79
- package/dist/ui/components/Bar.svelte +273 -255
- package/dist/ui/components/BarChart.svelte +58 -112
- package/dist/ui/components/BigValue.svelte +13 -7
- package/dist/ui/components/Chart.svelte +280 -317
- package/dist/ui/components/Column.svelte +102 -113
- package/dist/ui/components/DateRange.svelte +37 -27
- package/dist/ui/components/Dropdown.svelte +77 -57
- package/dist/ui/components/DropdownOption.svelte +10 -7
- package/dist/ui/components/ECharts.svelte +23 -16
- package/dist/ui/components/ErrorChart.svelte +85 -21
- package/dist/ui/components/GrapheneQuery.svelte +7 -3
- package/dist/ui/components/InlineDelta.svelte +53 -34
- package/dist/ui/components/Line.svelte +192 -178
- package/dist/ui/components/LineChart.svelte +53 -96
- package/dist/ui/components/PieChart.svelte +26 -15
- package/dist/ui/components/QueryLoad.svelte +15 -10
- package/dist/ui/components/SortIcon.svelte +5 -1
- package/dist/ui/components/Table.svelte +15 -9
- package/dist/ui/components/TableCell.svelte +30 -17
- package/dist/ui/components/TableGroupRow.svelte +26 -19
- package/dist/ui/components/TableGroupToggle.svelte +9 -6
- package/dist/ui/components/TableHeader.svelte +37 -27
- package/dist/ui/components/TableRow.svelte +30 -20
- package/dist/ui/components/TableSubtotalRow.svelte +16 -9
- package/dist/ui/components/TableTotalRow.svelte +18 -11
- package/dist/ui/components/TextInput.svelte +23 -20
- package/dist/ui/components/_Table.svelte +303 -260
- package/dist/ui/internal/LocalApp.svelte +40 -0
- package/dist/ui/internal/NavSidebar.svelte +27 -30
- package/dist/ui/internal/PageError.svelte +23 -0
- package/dist/ui/internal/checkSocket.ts +48 -0
- package/dist/ui/internal/queryEngine.ts +9 -2
- package/dist/ui/internal/telemetry.ts +1 -0
- package/dist/ui/web.js +5 -55
- package/package.json +9 -10
- package/cli.ts +0 -156
- package/dist/ui/internal/NavSidebarHMR.svelte +0 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {setContext, untrack, type Snippet} from 'svelte'
|
|
2
3
|
import {writable} from 'svelte/store'
|
|
3
|
-
import {setContext} from 'svelte'
|
|
4
4
|
import {propKey, strictBuild} from '../component-utilities/chartContext.js'
|
|
5
5
|
import getColumnSummary from '../component-utilities/getColumnSummary.js'
|
|
6
6
|
import {convertColumnToDate} from '../component-utilities/dateParsing.js'
|
|
@@ -15,237 +15,189 @@
|
|
|
15
15
|
import {getFinalColumnOrder} from '../component-utilities/tableUtils'
|
|
16
16
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
17
17
|
import {toBoolean} from '../component-utilities/convert'
|
|
18
|
-
|
|
18
|
+
import {logError} from '../internal/telemetry.js'
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
data?: any[], rows?: number | string, title?: string, subtitle?: string, rowNumbers?: boolean | string
|
|
22
|
+
sort?: string, sortable?: boolean | string, groupBy?: string, groupsOpen?: boolean | string
|
|
23
|
+
groupType?: 'accordion' | 'section', accordionRowColor?: string, groupNamePosition?: 'top' | 'middle' | 'bottom'
|
|
24
|
+
subtotals?: boolean | string, subtotalRowColor?: string, subtotalFontColor?: string
|
|
25
|
+
rowShading?: boolean | string, rowLines?: boolean | string, wrapTitles?: boolean | string
|
|
26
|
+
headerColor?: string, headerFontColor?: string, formatColumnTitles?: boolean | string
|
|
27
|
+
backgroundColor?: string, compact?: boolean | string, link?: string, showLinkCol?: boolean | string
|
|
28
|
+
totalRow?: boolean | string, totalRowColor?: string, totalFontColor?: string, emptyMessage?: string
|
|
29
|
+
isFullPage?: boolean | string, children?: Snippet
|
|
30
|
+
}
|
|
19
31
|
|
|
20
32
|
const {resolveColor} = getThemeStores()
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
let finalColumnOrder:
|
|
89
|
-
let orderedColumns
|
|
90
|
-
let dataForDisplay: any[] = []
|
|
91
|
-
let displayedRows: any[] = []
|
|
92
|
-
|
|
93
|
-
$: priorityColumns = [groupBy]
|
|
94
|
-
$: props.update((state) => ({...state, priorityColumns}))
|
|
95
|
-
$: finalColumnOrder = getFinalColumnOrder(($props.columns ?? []).map((column: any) => column.id), priorityColumns)
|
|
96
|
-
$: orderedColumns = [...($props.columns ?? [])].sort(
|
|
34
|
+
let {
|
|
35
|
+
data = [], rows = 10, title = undefined, subtitle = undefined, rowNumbers = false, sort = undefined,
|
|
36
|
+
sortable = true, groupBy = undefined, groupsOpen = true, groupType = 'accordion',
|
|
37
|
+
accordionRowColor = undefined, groupNamePosition = 'middle', subtotals = false,
|
|
38
|
+
subtotalRowColor = undefined, subtotalFontColor = undefined, rowShading = false, rowLines = true,
|
|
39
|
+
wrapTitles = false, headerColor = undefined, headerFontColor = undefined, formatColumnTitles = true,
|
|
40
|
+
backgroundColor = undefined, compact = undefined, link = undefined, showLinkCol = false,
|
|
41
|
+
totalRow = false, totalRowColor = undefined, totalFontColor = undefined, emptyMessage = undefined,
|
|
42
|
+
isFullPage = undefined, children,
|
|
43
|
+
}: Props = $props()
|
|
44
|
+
|
|
45
|
+
let rowsNum = $derived.by(() => {
|
|
46
|
+
let parsed = Number.parseInt(String(rows), 10)
|
|
47
|
+
return (!Number.isFinite(parsed) || parsed <= 0) ? 10 : parsed
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
let rowNumbersBool = $derived(toBoolean(rowNumbers) ?? false)
|
|
51
|
+
let groupsOpenBool = $derived(toBoolean(groupsOpen) ?? true)
|
|
52
|
+
let subtotalsBool = $derived(toBoolean(subtotals) ?? false)
|
|
53
|
+
let rowShadingBool = $derived(toBoolean(rowShading) ?? false)
|
|
54
|
+
let rowLinesBool = $derived(toBoolean(rowLines) ?? true)
|
|
55
|
+
let wrapTitlesBool = $derived(toBoolean(wrapTitles) ?? false)
|
|
56
|
+
let formatColumnTitlesBool = $derived(toBoolean(formatColumnTitles) ?? true)
|
|
57
|
+
let compactBool = $derived(toBoolean(compact))
|
|
58
|
+
let showLinkColBool = $derived(toBoolean(showLinkCol) ?? false)
|
|
59
|
+
let totalRowBool = $derived(toBoolean(totalRow) ?? false)
|
|
60
|
+
let sortableBool = $derived(toBoolean(sortable) ?? true)
|
|
61
|
+
let isFullPageBool = $derived(toBoolean(isFullPage) ?? false)
|
|
62
|
+
|
|
63
|
+
let effectiveRowNumbers = $derived(groupType === 'section' ? false : rowNumbersBool)
|
|
64
|
+
|
|
65
|
+
// Initialize store without data - it will be populated by the effect below
|
|
66
|
+
const tablePropsStore = writable<{data: any[]; columns: any[]; priorityColumns:(string | undefined)[]}>({data: [], columns: [], priorityColumns: []})
|
|
67
|
+
setContext(propKey, tablePropsStore)
|
|
68
|
+
|
|
69
|
+
// Update store when data or groupBy changes
|
|
70
|
+
$effect(() => {
|
|
71
|
+
// Track data and groupBy
|
|
72
|
+
let currentData = data
|
|
73
|
+
let currentGroupBy = groupBy
|
|
74
|
+
untrack(() => {
|
|
75
|
+
tablePropsStore.update((state) => ({...state, data: currentData, priorityColumns: [currentGroupBy]}))
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
let accordionRowColorStore = $derived(resolveColor(accordionRowColor))
|
|
80
|
+
let subtotalRowColorStore = $derived(resolveColor(subtotalRowColor))
|
|
81
|
+
let subtotalFontColorStore = $derived(resolveColor(subtotalFontColor))
|
|
82
|
+
let totalRowColorStore = $derived(resolveColor(totalRowColor))
|
|
83
|
+
let totalFontColorStore = $derived(resolveColor(totalFontColor))
|
|
84
|
+
let headerColorStore = $derived(resolveColor(headerColor))
|
|
85
|
+
let headerFontColorStore = $derived(resolveColor(headerFontColor))
|
|
86
|
+
let backgroundColorStore = $derived(resolveColor(backgroundColor))
|
|
87
|
+
|
|
88
|
+
let priorityColumns = $derived<(string | undefined)[]>([groupBy])
|
|
89
|
+
|
|
90
|
+
$effect(() => {
|
|
91
|
+
void priorityColumns
|
|
92
|
+
untrack(() => {
|
|
93
|
+
tablePropsStore.update((state) => ({...state, priorityColumns}))
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Use $derived to reactively read from the store
|
|
98
|
+
let tablePropsColumns = $derived($tablePropsStore.columns ?? [])
|
|
99
|
+
|
|
100
|
+
let finalColumnOrder = $derived(getFinalColumnOrder(tablePropsColumns.map((column: any) => column.id), priorityColumns))
|
|
101
|
+
let orderedColumns = $derived([...tablePropsColumns].sort(
|
|
97
102
|
(a, b) => finalColumnOrder.indexOf(a.id) - finalColumnOrder.indexOf(b.id),
|
|
98
|
-
)
|
|
103
|
+
))
|
|
99
104
|
|
|
100
|
-
let sortObj: {col: string | null; ascending: boolean | null} = {col: null, ascending: null}
|
|
101
|
-
let sortBy: string | undefined
|
|
102
|
-
let sortAsc: boolean | undefined
|
|
105
|
+
let sortObj: {col: string | null; ascending: boolean | null} = $state({col: null, ascending: null})
|
|
103
106
|
|
|
104
|
-
|
|
107
|
+
// Parse initial sort on mount
|
|
108
|
+
let initialSort = $derived.by(() => {
|
|
109
|
+
if (!sort) return {sortBy: undefined, sortAsc: true}
|
|
105
110
|
let [column, direction] = sort.split(/\s+/)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
sortAsc
|
|
109
|
-
} else {
|
|
110
|
-
sortAsc = true
|
|
111
|
-
}
|
|
112
|
-
sortObj = sortBy ? {col: sortBy, ascending: sortAsc ?? true} : {col: null, ascending: null}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
$: props.update((state) => ({...state, data}))
|
|
116
|
-
|
|
117
|
-
$: try {
|
|
118
|
-
error = undefined
|
|
119
|
-
checkInputs(Array.isArray(data) ? data : [])
|
|
120
|
-
columnSummary = getColumnSummary(data ?? [], 'array')
|
|
121
|
-
|
|
122
|
-
if (sortBy) {
|
|
123
|
-
let columnNames = columnSummary.map((col) => col.id)
|
|
124
|
-
if (!columnNames.includes(sortBy)) {
|
|
125
|
-
throw new Error(`${sortBy} is not a column in the dataset. sort should contain one column name and optionally a direction (asc or desc).`)
|
|
126
|
-
}
|
|
111
|
+
return {
|
|
112
|
+
sortBy: column,
|
|
113
|
+
sortAsc: direction ? direction.toLowerCase() !== 'desc' : true,
|
|
127
114
|
}
|
|
115
|
+
})
|
|
128
116
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for (let columnId of dateCols) {
|
|
134
|
-
data = convertColumnToDate(data, columnId)
|
|
117
|
+
// Initialize sortObj when sort prop changes
|
|
118
|
+
$effect(() => {
|
|
119
|
+
if (sort && initialSort.sortBy) {
|
|
120
|
+
sortObj = {col: initialSort.sortBy, ascending: initialSort.sortAsc}
|
|
135
121
|
}
|
|
136
|
-
|
|
137
|
-
if (link && !showLinkCol) {
|
|
138
|
-
let linkIndex = columnSummary.findIndex((col) => col.id === link)
|
|
139
|
-
if (linkIndex !== -1) {
|
|
140
|
-
columnSummary = [...columnSummary.slice(0, linkIndex), ...columnSummary.slice(linkIndex + 1)]
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
} catch (thrown) {
|
|
144
|
-
let message = thrown instanceof Error ? thrown.message : 'Unable to prepare dataset'
|
|
145
|
-
logError(thrown, {id: 'DataTable'})
|
|
146
|
-
error = message
|
|
147
|
-
if (strictBuild) throw thrown
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
let paginated = false
|
|
151
|
-
let currentPage = 1
|
|
152
|
-
let pageCount = 1
|
|
153
|
-
let displayedPageLength = 0
|
|
154
|
-
|
|
155
|
-
const goToPage = (page: number) => {
|
|
156
|
-
if (!paginated) return
|
|
157
|
-
let next = Math.min(Math.max(page, 1), pageCount)
|
|
158
|
-
if (Number.isFinite(next)) currentPage = next
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let groupedData: Record<string, any[]> = {}
|
|
162
|
-
let groupToggleStates: Record<string, boolean> = {}
|
|
122
|
+
})
|
|
163
123
|
|
|
164
124
|
const coerceId = (value: any) => {
|
|
165
125
|
if (value === undefined || value === null || value === '') return undefined
|
|
166
126
|
return String(value)
|
|
167
127
|
}
|
|
168
128
|
|
|
169
|
-
|
|
129
|
+
// Process data - combine all data processing into one $derived.by block to avoid loops
|
|
130
|
+
let processedState = $derived.by(() => {
|
|
131
|
+
let resultError: string | undefined = undefined
|
|
132
|
+
let resultColumnSummary: any[] = []
|
|
133
|
+
let resultProcessedData: any[] = []
|
|
134
|
+
let resultDataTestId: string | undefined = undefined
|
|
135
|
+
let resultNormalizedData: any[] = []
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// First normalize data - extract rows array from object if needed
|
|
139
|
+
let inputData: any[]
|
|
140
|
+
if (!Array.isArray(data)) {
|
|
141
|
+
let raw = data as any
|
|
142
|
+
resultDataTestId = coerceId(raw?.id)
|
|
143
|
+
let candidate = raw?.rows
|
|
144
|
+
inputData = Array.isArray(candidate) ? candidate : []
|
|
145
|
+
} else {
|
|
146
|
+
resultDataTestId = coerceId((data as any)?.id)
|
|
147
|
+
inputData = data
|
|
148
|
+
}
|
|
170
149
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
let raw = data as any
|
|
174
|
-
dataTestId = coerceId(raw?.id)
|
|
175
|
-
let candidate = raw?.rows
|
|
176
|
-
data = Array.isArray(candidate) ? candidate : []
|
|
177
|
-
} else {
|
|
178
|
-
dataTestId = coerceId((data as any)?.id)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
150
|
+
checkInputs(inputData)
|
|
151
|
+
resultColumnSummary = getColumnSummary(inputData, 'array')
|
|
181
152
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
153
|
+
if (initialSort.sortBy) {
|
|
154
|
+
let columnNames = resultColumnSummary.map((col) => col.id)
|
|
155
|
+
if (!columnNames.includes(initialSort.sortBy)) {
|
|
156
|
+
throw new Error(`${initialSort.sortBy} is not a column in the dataset. sort should contain one column name and optionally a direction (asc or desc).`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
188
159
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let key = groupName ?? '(blank)'
|
|
193
|
-
if (!acc[key]) acc[key] = []
|
|
194
|
-
acc[key].push(row)
|
|
195
|
-
return acc
|
|
196
|
-
}, {})
|
|
160
|
+
let dateCols = resultColumnSummary
|
|
161
|
+
.filter((col) => col.type === 'date' && !(inputData?.[0]?.[col.id] instanceof Date))
|
|
162
|
+
.map((col) => col.id)
|
|
197
163
|
|
|
198
|
-
|
|
199
|
-
|
|
164
|
+
let tempData = inputData
|
|
165
|
+
for (let columnId of dateCols) {
|
|
166
|
+
tempData = convertColumnToDate(tempData, columnId)
|
|
167
|
+
}
|
|
168
|
+
resultProcessedData = tempData
|
|
169
|
+
resultNormalizedData = tempData
|
|
170
|
+
|
|
171
|
+
if (link && !showLinkColBool) {
|
|
172
|
+
let linkIndex = resultColumnSummary.findIndex((col) => col.id === link)
|
|
173
|
+
if (linkIndex !== -1) {
|
|
174
|
+
resultColumnSummary = [...resultColumnSummary.slice(0, linkIndex), ...resultColumnSummary.slice(linkIndex + 1)]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (thrown) {
|
|
178
|
+
let message = thrown instanceof Error ? thrown.message : 'Unable to prepare dataset'
|
|
179
|
+
logError(thrown, {id: 'DataTable'})
|
|
180
|
+
resultError = message
|
|
181
|
+
if (strictBuild) throw thrown
|
|
200
182
|
}
|
|
201
|
-
} else {
|
|
202
|
-
groupedData = {}
|
|
203
|
-
}
|
|
204
183
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (groupBy) {
|
|
212
|
-
displayedRows = data ?? []
|
|
213
|
-
} else if (paginated) {
|
|
214
|
-
let start = rows * (currentPage - 1)
|
|
215
|
-
let end = start + rows
|
|
216
|
-
displayedRows = dataForDisplay?.slice(start, end) ?? []
|
|
217
|
-
} else {
|
|
218
|
-
displayedRows = dataForDisplay ?? []
|
|
184
|
+
return {
|
|
185
|
+
error: resultError,
|
|
186
|
+
columnSummary: resultColumnSummary,
|
|
187
|
+
processedData: resultProcessedData,
|
|
188
|
+
dataTestId: resultDataTestId,
|
|
189
|
+
normalizedData: resultNormalizedData,
|
|
219
190
|
}
|
|
220
|
-
}
|
|
191
|
+
})
|
|
221
192
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
groupedData = Object.fromEntries(
|
|
227
|
-
Object.entries(groupedData).map(([groupName, rows]) => [
|
|
228
|
-
groupName,
|
|
229
|
-
[...rows].sort((a, b) => compareValues(a[state.col as string], b[state.col as string], ascending)),
|
|
230
|
-
]),
|
|
231
|
-
)
|
|
232
|
-
} else {
|
|
233
|
-
let source = Array.isArray(data) ? data : []
|
|
234
|
-
dataForDisplay = [...source].sort((a, b) => compareValues(a[state.col as string], b[state.col as string], ascending))
|
|
235
|
-
}
|
|
236
|
-
}
|
|
193
|
+
// Extract processed state
|
|
194
|
+
let columnSummary = $derived(processedState.columnSummary)
|
|
195
|
+
let normalizedData = $derived(processedState.normalizedData)
|
|
196
|
+
let dataTestId = $derived(processedState.dataTestId)
|
|
237
197
|
|
|
238
|
-
|
|
239
|
-
if (!sortable) return
|
|
240
|
-
if (!column) return
|
|
241
|
-
if (sortObj.col === column) {
|
|
242
|
-
sortObj = {col: column, ascending: !sortObj.ascending}
|
|
243
|
-
} else {
|
|
244
|
-
sortObj = {col: column, ascending: true}
|
|
245
|
-
}
|
|
246
|
-
applySort(sortObj)
|
|
247
|
-
}
|
|
198
|
+
let error = $derived(processedState.error)
|
|
248
199
|
|
|
200
|
+
// Sorting helpers
|
|
249
201
|
const normalizeForSort = (value: unknown) => {
|
|
250
202
|
if (value instanceof Date) return value.getTime()
|
|
251
203
|
if (typeof value === 'number') return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY
|
|
@@ -273,51 +225,142 @@
|
|
|
273
225
|
return 0
|
|
274
226
|
}
|
|
275
227
|
|
|
276
|
-
|
|
277
|
-
|
|
228
|
+
// Compute dataForDisplay as derived to avoid effect loops
|
|
229
|
+
let dataForDisplay = $derived.by(() => {
|
|
230
|
+
let source = Array.isArray(normalizedData) ? normalizedData : []
|
|
278
231
|
if (groupBy) {
|
|
279
|
-
|
|
232
|
+
return source
|
|
280
233
|
} else if (sortObj.col) {
|
|
281
234
|
let ascending = sortObj.ascending ?? true
|
|
282
|
-
|
|
235
|
+
return [...source].sort((a, b) => compareValues(a[sortObj.col as string], b[sortObj.col as string], ascending))
|
|
283
236
|
} else {
|
|
284
|
-
|
|
237
|
+
return source
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// Pagination state and derived values
|
|
242
|
+
let currentPage = $state(1)
|
|
243
|
+
let paginated = $derived(!groupBy && rowsNum > 0 && (dataForDisplay?.length ?? 0) > rowsNum)
|
|
244
|
+
let pageCount = $derived(paginated ? Math.ceil((dataForDisplay?.length ?? 0) / rowsNum) : 1)
|
|
245
|
+
|
|
246
|
+
// Clamp currentPage when pageCount changes - but only update if needed
|
|
247
|
+
$effect(() => {
|
|
248
|
+
let clamped = Math.min(Math.max(currentPage, 1), pageCount)
|
|
249
|
+
if (clamped !== currentPage) {
|
|
250
|
+
untrack(() => {
|
|
251
|
+
currentPage = clamped
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
let displayedPageLength = $derived(paginated
|
|
257
|
+
? Math.min(rowsNum, (dataForDisplay?.length ?? 0) - rowsNum * (currentPage - 1))
|
|
258
|
+
: dataForDisplay?.length ?? 0)
|
|
259
|
+
|
|
260
|
+
const goToPage = (page: number) => {
|
|
261
|
+
if (!paginated) return
|
|
262
|
+
let next = Math.min(Math.max(page, 1), pageCount)
|
|
263
|
+
if (Number.isFinite(next)) currentPage = next
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let groupToggleStates: Record<string, boolean> = $state({})
|
|
267
|
+
|
|
268
|
+
// Compute grouped data as derived
|
|
269
|
+
let groupedData = $derived.by(() => {
|
|
270
|
+
if (!groupBy || !normalizedData) return {}
|
|
271
|
+
return normalizedData.reduce<Record<string, any[]>>((acc, row) => {
|
|
272
|
+
let groupName = row[groupBy]
|
|
273
|
+
let key = groupName ?? '(blank)'
|
|
274
|
+
if (!acc[key]) acc[key] = []
|
|
275
|
+
acc[key].push(row)
|
|
276
|
+
return acc
|
|
277
|
+
}, {})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Initialize toggle states for new groups
|
|
281
|
+
$effect(() => {
|
|
282
|
+
if (groupBy && groupedData) {
|
|
283
|
+
for (let name of Object.keys(groupedData)) {
|
|
284
|
+
if (!(name in groupToggleStates)) {
|
|
285
|
+
untrack(() => {
|
|
286
|
+
groupToggleStates = {...groupToggleStates, [name]: groupsOpenBool}
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
}
|
|
285
290
|
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const handleToggle = (event: CustomEvent<{groupName: string}>) => {
|
|
294
|
+
let {groupName} = event.detail
|
|
295
|
+
groupToggleStates = {...groupToggleStates, [groupName]: !groupToggleStates[groupName]}
|
|
286
296
|
}
|
|
287
297
|
|
|
288
|
-
|
|
298
|
+
// Compute displayedRows as derived
|
|
299
|
+
let displayedRows = $derived.by(() => {
|
|
300
|
+
if (groupBy) {
|
|
301
|
+
return normalizedData ?? []
|
|
302
|
+
} else if (paginated) {
|
|
303
|
+
let start = rowsNum * (currentPage - 1)
|
|
304
|
+
let end = start + rowsNum
|
|
305
|
+
return dataForDisplay?.slice(start, end) ?? []
|
|
306
|
+
} else {
|
|
307
|
+
return dataForDisplay ?? []
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Sort grouped data when sortObj changes
|
|
312
|
+
let sortedGroupedData = $derived.by(() => {
|
|
313
|
+
if (!groupBy || !sortObj.col) return groupedData
|
|
314
|
+
let ascending = sortObj.ascending ?? true
|
|
315
|
+
return Object.fromEntries(
|
|
316
|
+
Object.entries(groupedData).map(([name, rows]) => [
|
|
317
|
+
name,
|
|
318
|
+
[...rows].sort((a, b) => compareValues(a[sortObj.col as string], b[sortObj.col as string], ascending)),
|
|
319
|
+
]),
|
|
320
|
+
)
|
|
321
|
+
})
|
|
289
322
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
323
|
+
const sortClick = (column: string) => () => {
|
|
324
|
+
if (!sortableBool) return
|
|
325
|
+
if (!column) return
|
|
326
|
+
if (sortObj.col === column) {
|
|
327
|
+
sortObj = {col: column, ascending: !sortObj.ascending}
|
|
328
|
+
} else {
|
|
329
|
+
sortObj = {col: column, ascending: true}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
293
332
|
|
|
294
|
-
let
|
|
295
|
-
|
|
333
|
+
let sortedGroupNames = $derived(groupBy
|
|
334
|
+
? Object.keys(sortedGroupedData).sort((a, b) => a.localeCompare(b))
|
|
335
|
+
: [])
|
|
336
|
+
|
|
337
|
+
let groupOffsets = $derived.by(() => {
|
|
338
|
+
if (!groupBy) return {}
|
|
296
339
|
let running = 0
|
|
297
|
-
|
|
340
|
+
let offsets: Record<string, number> = {}
|
|
298
341
|
for (let name of sortedGroupNames) {
|
|
299
|
-
|
|
300
|
-
running +=
|
|
342
|
+
offsets[name] = running
|
|
343
|
+
running += sortedGroupedData[name]?.length ?? 0
|
|
301
344
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
345
|
+
return offsets
|
|
346
|
+
})
|
|
305
347
|
|
|
306
|
-
let totalRows = 0
|
|
307
|
-
|
|
308
|
-
$: tableData = dataForDisplay ?? []
|
|
348
|
+
let totalRows = $derived(dataForDisplay?.length ?? 0)
|
|
349
|
+
let tableData = $derived(dataForDisplay ?? [])
|
|
309
350
|
</script>
|
|
310
351
|
|
|
311
352
|
{#if !error}
|
|
312
|
-
|
|
353
|
+
{#if children}
|
|
354
|
+
{@render children()}
|
|
355
|
+
{:else}
|
|
313
356
|
{#each columnSummary as column (column.id)}
|
|
314
357
|
<Column id={column.id} />
|
|
315
358
|
{/each}
|
|
316
|
-
|
|
359
|
+
{/if}
|
|
317
360
|
|
|
318
361
|
<div
|
|
319
362
|
class={`table-container ${paginated ? 'table-container--has-pagination' : ''}`}
|
|
320
|
-
data-testid={
|
|
363
|
+
data-testid={isFullPageBool ? undefined : `DataTable-${dataTestId ?? 'no-id'}`}
|
|
321
364
|
>
|
|
322
365
|
{#if title || subtitle}
|
|
323
366
|
<div class="table-title">
|
|
@@ -329,17 +372,17 @@
|
|
|
329
372
|
<div class="scrollbox pretty-scrollbar" style:background-color={$backgroundColorStore}>
|
|
330
373
|
<table>
|
|
331
374
|
<TableHeader
|
|
332
|
-
{
|
|
375
|
+
rowNumbers={effectiveRowNumbers}
|
|
333
376
|
headerColor={$headerColorStore}
|
|
334
377
|
headerFontColor={$headerFontColorStore}
|
|
335
378
|
{orderedColumns}
|
|
336
379
|
{columnSummary}
|
|
337
|
-
{
|
|
380
|
+
sortable={sortableBool}
|
|
338
381
|
{sortClick}
|
|
339
|
-
{
|
|
382
|
+
formatColumnTitles={formatColumnTitlesBool}
|
|
340
383
|
{sortObj}
|
|
341
|
-
{
|
|
342
|
-
{
|
|
384
|
+
wrapTitles={wrapTitlesBool}
|
|
385
|
+
compact={compactBool}
|
|
343
386
|
link={link}
|
|
344
387
|
/>
|
|
345
388
|
|
|
@@ -348,25 +391,25 @@
|
|
|
348
391
|
{#if groupType !== 'section'}
|
|
349
392
|
<TableGroupRow
|
|
350
393
|
{groupName}
|
|
351
|
-
currentGroupData={
|
|
394
|
+
currentGroupData={sortedGroupedData[groupName]}
|
|
352
395
|
toggled={groupToggleStates[groupName]}
|
|
353
396
|
{columnSummary}
|
|
354
|
-
{
|
|
397
|
+
rowNumbers={effectiveRowNumbers}
|
|
355
398
|
rowColor={$accordionRowColorStore}
|
|
356
|
-
{
|
|
357
|
-
|
|
399
|
+
subtotals={subtotalsBool}
|
|
400
|
+
ontoggle={handleToggle}
|
|
358
401
|
{orderedColumns}
|
|
359
|
-
{
|
|
402
|
+
compact={compactBool}
|
|
360
403
|
/>
|
|
361
404
|
{/if}
|
|
362
405
|
{#if groupType === 'section' || groupToggleStates[groupName]}
|
|
363
406
|
<TableRow
|
|
364
|
-
displayedData={
|
|
365
|
-
{
|
|
407
|
+
displayedData={sortedGroupedData[groupName]}
|
|
408
|
+
rowShading={rowShadingBool}
|
|
366
409
|
{link}
|
|
367
|
-
{
|
|
368
|
-
{
|
|
369
|
-
{
|
|
410
|
+
rowNumbers={effectiveRowNumbers}
|
|
411
|
+
rowLines={rowLinesBool}
|
|
412
|
+
compact={compactBool}
|
|
370
413
|
{columnSummary}
|
|
371
414
|
grouped={true}
|
|
372
415
|
{groupType}
|
|
@@ -374,19 +417,19 @@
|
|
|
374
417
|
groupNamePosition={groupNamePosition}
|
|
375
418
|
orderedColumns={orderedColumns}
|
|
376
419
|
index={groupOffsets[groupName] ?? 0}
|
|
377
|
-
rowSpan={
|
|
420
|
+
rowSpan={sortedGroupedData[groupName].length}
|
|
378
421
|
/>
|
|
379
|
-
{#if
|
|
422
|
+
{#if subtotalsBool}
|
|
380
423
|
<TableSubtotalRow
|
|
381
424
|
{groupName}
|
|
382
|
-
currentGroupData={
|
|
425
|
+
currentGroupData={sortedGroupedData[groupName]}
|
|
383
426
|
{columnSummary}
|
|
384
427
|
rowColor={$subtotalRowColorStore}
|
|
385
428
|
fontColor={$subtotalFontColorStore}
|
|
386
429
|
groupBy={groupBy}
|
|
387
430
|
groupType={groupType}
|
|
388
431
|
{orderedColumns}
|
|
389
|
-
{
|
|
432
|
+
compact={compactBool}
|
|
390
433
|
/>
|
|
391
434
|
{/if}
|
|
392
435
|
{/if}
|
|
@@ -394,31 +437,31 @@
|
|
|
394
437
|
{:else}
|
|
395
438
|
<TableRow
|
|
396
439
|
displayedData={displayedRows}
|
|
397
|
-
{
|
|
440
|
+
rowShading={rowShadingBool}
|
|
398
441
|
{link}
|
|
399
|
-
{
|
|
400
|
-
{
|
|
401
|
-
{
|
|
442
|
+
rowNumbers={effectiveRowNumbers}
|
|
443
|
+
rowLines={rowLinesBool}
|
|
444
|
+
compact={compactBool}
|
|
402
445
|
{columnSummary}
|
|
403
446
|
grouped={false}
|
|
404
447
|
{groupType}
|
|
405
448
|
groupColumn={groupBy}
|
|
406
449
|
groupNamePosition={groupNamePosition}
|
|
407
450
|
orderedColumns={orderedColumns}
|
|
408
|
-
index={
|
|
451
|
+
index={rowsNum * (currentPage - 1)}
|
|
409
452
|
/>
|
|
410
453
|
{/if}
|
|
411
454
|
|
|
412
|
-
{#if
|
|
455
|
+
{#if totalRowBool && !groupBy}
|
|
413
456
|
<TableTotalRow
|
|
414
457
|
data={tableData}
|
|
415
|
-
{
|
|
458
|
+
rowNumbers={effectiveRowNumbers}
|
|
416
459
|
{columnSummary}
|
|
417
460
|
rowColor={$totalRowColorStore}
|
|
418
461
|
fontColor={$totalFontColorStore}
|
|
419
462
|
groupType={groupType}
|
|
420
463
|
{orderedColumns}
|
|
421
|
-
{
|
|
464
|
+
compact={compactBool}
|
|
422
465
|
/>
|
|
423
466
|
{/if}
|
|
424
467
|
</table>
|
|
@@ -426,13 +469,13 @@
|
|
|
426
469
|
|
|
427
470
|
{#if paginated && pageCount > 1}
|
|
428
471
|
<div class="pagination">
|
|
429
|
-
<button class="pagination__button" disabled={currentPage === 1}
|
|
430
|
-
<button class="pagination__button" disabled={currentPage === 1}
|
|
472
|
+
<button class="pagination__button" disabled={currentPage === 1} onclick={() => goToPage(1)}>First</button>
|
|
473
|
+
<button class="pagination__button" disabled={currentPage === 1} onclick={() => goToPage(currentPage - 1)}>Prev</button>
|
|
431
474
|
<div class="pagination__status">
|
|
432
475
|
Page {currentPage.toLocaleString()} of {pageCount.toLocaleString()}
|
|
433
476
|
</div>
|
|
434
|
-
<button class="pagination__button" disabled={currentPage === pageCount}
|
|
435
|
-
<button class="pagination__button" disabled={currentPage === pageCount}
|
|
477
|
+
<button class="pagination__button" disabled={currentPage === pageCount} onclick={() => goToPage(currentPage + 1)}>Next</button>
|
|
478
|
+
<button class="pagination__button" disabled={currentPage === pageCount} onclick={() => goToPage(pageCount)}>Last</button>
|
|
436
479
|
<div class="pagination__meta">{displayedPageLength.toLocaleString()} of {totalRows.toLocaleString()} rows</div>
|
|
437
480
|
</div>
|
|
438
481
|
{/if}
|