@graphenedata/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +100 -0
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/cli.ts +157 -0
- package/dist/cli/cli.js +43 -0
- package/dist/docs/data_apps/components/charts/annotations.md +673 -0
- package/dist/docs/data_apps/components/charts/area-chart.md +202 -0
- package/dist/docs/data_apps/components/charts/bar-chart.md +317 -0
- package/dist/docs/data_apps/components/charts/box-plot.md +190 -0
- package/dist/docs/data_apps/components/charts/bubble-chart.md +151 -0
- package/dist/docs/data_apps/components/charts/calendar-heatmap.md +112 -0
- package/dist/docs/data_apps/components/charts/custom-echarts.md +308 -0
- package/dist/docs/data_apps/components/charts/echarts-options.md +217 -0
- package/dist/docs/data_apps/components/charts/funnel-chart.md +106 -0
- package/dist/docs/data_apps/components/charts/heatmap.md +180 -0
- package/dist/docs/data_apps/components/charts/histogram.md +107 -0
- package/dist/docs/data_apps/components/charts/line-chart.md +265 -0
- package/dist/docs/data_apps/components/charts/mixed-type-charts.md +240 -0
- package/dist/docs/data_apps/components/charts/sankey-diagram.md +301 -0
- package/dist/docs/data_apps/components/charts/scatter-plot.md +134 -0
- package/dist/docs/data_apps/components/charts/sparkline.md +68 -0
- package/dist/docs/data_apps/components/data/big-value.md +153 -0
- package/dist/docs/data_apps/components/data/delta.md +89 -0
- package/dist/docs/data_apps/components/data/table.md +470 -0
- package/dist/docs/data_apps/components/data/value.md +97 -0
- package/dist/docs/data_apps/components/inputs/button-group.md +154 -0
- package/dist/docs/data_apps/components/inputs/checkbox.md +52 -0
- package/dist/docs/data_apps/components/inputs/date-input.md +131 -0
- package/dist/docs/data_apps/components/inputs/date-range.md +124 -0
- package/dist/docs/data_apps/components/inputs/dimension-grid.md +67 -0
- package/dist/docs/data_apps/components/inputs/dropdown.md +199 -0
- package/dist/docs/data_apps/components/inputs/index.md +3 -0
- package/dist/docs/data_apps/components/inputs/slider.md +126 -0
- package/dist/docs/data_apps/components/inputs/text-input.md +86 -0
- package/dist/docs/data_apps/components/maps/area-map.md +397 -0
- package/dist/docs/data_apps/components/maps/base-map.md +269 -0
- package/dist/docs/data_apps/components/maps/bubble-map.md +361 -0
- package/dist/docs/data_apps/components/maps/point-map.md +326 -0
- package/dist/docs/data_apps/components/maps/us-map.md +167 -0
- package/dist/docs/data_apps/components/ui/accordion.md +116 -0
- package/dist/docs/data_apps/components/ui/alert.md +37 -0
- package/dist/docs/data_apps/components/ui/big-link.md +19 -0
- package/dist/docs/data_apps/components/ui/details.md +58 -0
- package/dist/docs/data_apps/components/ui/download-data.md +41 -0
- package/dist/docs/data_apps/components/ui/embed.md +47 -0
- package/dist/docs/data_apps/components/ui/grid.md +45 -0
- package/dist/docs/data_apps/components/ui/image.md +61 -0
- package/dist/docs/data_apps/components/ui/info.md +47 -0
- package/dist/docs/data_apps/components/ui/last-refreshed.md +28 -0
- package/dist/docs/data_apps/components/ui/link-button.md +20 -0
- package/dist/docs/data_apps/components/ui/link.md +40 -0
- package/dist/docs/data_apps/components/ui/modal.md +57 -0
- package/dist/docs/data_apps/components/ui/note.md +32 -0
- package/dist/docs/data_apps/components/ui/print-format-components.md +85 -0
- package/dist/docs/data_apps/components/ui/tabs.md +122 -0
- package/dist/docs/graphene.md +129 -0
- package/dist/ui/app.css +332 -0
- package/dist/ui/assets/favicon.ico +0 -0
- package/dist/ui/component-utilities/autoFormatting.js +301 -0
- package/dist/ui/component-utilities/builtInFormats.js +482 -0
- package/dist/ui/component-utilities/chartContext.js +12 -0
- package/dist/ui/component-utilities/chartWindowDebug.js +21 -0
- package/dist/ui/component-utilities/checkInputs.js +95 -0
- package/dist/ui/component-utilities/convert.js +15 -0
- package/dist/ui/component-utilities/dateParsing.js +57 -0
- package/dist/ui/component-utilities/dropdownContext.ts +1 -0
- package/dist/ui/component-utilities/echarts.js +262 -0
- package/dist/ui/component-utilities/echartsThemes.js +453 -0
- package/dist/ui/component-utilities/formatTitle.js +24 -0
- package/dist/ui/component-utilities/formatting.js +258 -0
- package/dist/ui/component-utilities/getColumnExtents.js +79 -0
- package/dist/ui/component-utilities/getColumnSummary.js +67 -0
- package/dist/ui/component-utilities/getCompletedData.js +114 -0
- package/dist/ui/component-utilities/getDistinctCount.js +7 -0
- package/dist/ui/component-utilities/getDistinctValues.js +15 -0
- package/dist/ui/component-utilities/getSeriesConfig.js +236 -0
- package/dist/ui/component-utilities/getSortedData.js +7 -0
- package/dist/ui/component-utilities/getStackPercentages.js +43 -0
- package/dist/ui/component-utilities/getStackedData.js +17 -0
- package/dist/ui/component-utilities/getYAxisIndex.js +15 -0
- package/dist/ui/component-utilities/globalContexts.js +1 -0
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +119 -0
- package/dist/ui/component-utilities/inputUtils.ts +25 -0
- package/dist/ui/component-utilities/replaceNulls.js +14 -0
- package/dist/ui/component-utilities/tableUtils.ts +120 -0
- package/dist/ui/component-utilities/themeStores.ts +116 -0
- package/dist/ui/components/Area.svelte +174 -0
- package/dist/ui/components/AreaChart.svelte +150 -0
- package/dist/ui/components/Bar.svelte +326 -0
- package/dist/ui/components/BarChart.svelte +194 -0
- package/dist/ui/components/BigValue.svelte +69 -0
- package/dist/ui/components/Chart.svelte +1070 -0
- package/dist/ui/components/Column.svelte +172 -0
- package/dist/ui/components/DateRange.svelte +324 -0
- package/dist/ui/components/Dropdown.svelte +738 -0
- package/dist/ui/components/DropdownOption.svelte +21 -0
- package/dist/ui/components/ECharts.svelte +77 -0
- package/dist/ui/components/ErrorChart.svelte +54 -0
- package/dist/ui/components/GrapheneQuery.svelte +12 -0
- package/dist/ui/components/InlineDelta.svelte +150 -0
- package/dist/ui/components/Line.svelte +210 -0
- package/dist/ui/components/LineChart.svelte +178 -0
- package/dist/ui/components/PieChart.svelte +36 -0
- package/dist/ui/components/QueryLoad.svelte +82 -0
- package/dist/ui/components/Row.svelte +14 -0
- package/dist/ui/components/SortIcon.svelte +32 -0
- package/dist/ui/components/Table.svelte +19 -0
- package/dist/ui/components/TableCell.svelte +75 -0
- package/dist/ui/components/TableGroupRow.svelte +136 -0
- package/dist/ui/components/TableGroupToggle.svelte +42 -0
- package/dist/ui/components/TableHeader.svelte +242 -0
- package/dist/ui/components/TableRow.svelte +283 -0
- package/dist/ui/components/TableSubtotalRow.svelte +62 -0
- package/dist/ui/components/TableTotalRow.svelte +88 -0
- package/dist/ui/components/TextInput.svelte +92 -0
- package/dist/ui/components/_Table.svelte +516 -0
- package/dist/ui/internal/clientCache.ts +43 -0
- package/dist/ui/internal/queryEngine.ts +169 -0
- package/dist/ui/internal/telemetry.ts +28 -0
- package/dist/ui/internal/theme.ts +88 -0
- package/dist/ui/layout.svelte +3 -0
- package/dist/ui/playwright.config.ts +30 -0
- package/dist/ui/web.js +106 -0
- package/package.json +71 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script context="module">
|
|
2
|
+
export const evidenceInclude = true
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import {getContext, onDestroy} from 'svelte'
|
|
7
|
+
import {propKey, strictBuild} from '../component-utilities/chartContext.js'
|
|
8
|
+
import {getThemeStores} from '../component-utilities/themeStores'
|
|
9
|
+
import {toBoolean} from '../component-utilities/convert'
|
|
10
|
+
|
|
11
|
+
export let id: string
|
|
12
|
+
export let description: string | undefined = undefined
|
|
13
|
+
export let contentType: string | undefined = undefined
|
|
14
|
+
export let title: string | undefined = undefined
|
|
15
|
+
export let align: string | undefined = undefined
|
|
16
|
+
export let wrap: boolean | string | undefined = undefined
|
|
17
|
+
export let wrapTitle: boolean | string | undefined = undefined
|
|
18
|
+
export let height: string | undefined = undefined
|
|
19
|
+
export let width: string | undefined = undefined
|
|
20
|
+
export let alt: string | undefined = undefined
|
|
21
|
+
export let openInNewTab: boolean | string | undefined = undefined
|
|
22
|
+
export let linkLabel: string | undefined = undefined
|
|
23
|
+
export let fmt: string | undefined = undefined
|
|
24
|
+
export let totalAgg: string | undefined = undefined
|
|
25
|
+
export let totalFmt: string | undefined = undefined
|
|
26
|
+
export let weightCol: string | undefined = undefined
|
|
27
|
+
export let subtotalFmt: string | undefined = undefined
|
|
28
|
+
export let colorMax: string | undefined = undefined
|
|
29
|
+
export let colorMin: string | undefined = undefined
|
|
30
|
+
export let colorMid: string | undefined = undefined
|
|
31
|
+
export let colorBreakpoints: string[] | undefined = undefined
|
|
32
|
+
export let colorScale: any = 'default'
|
|
33
|
+
export let scaleColumn: string | undefined = undefined
|
|
34
|
+
export let downIsGood: boolean | string | undefined = undefined
|
|
35
|
+
export let showValue: boolean | string | undefined = undefined
|
|
36
|
+
export let deltaSymbol: boolean | string | undefined = undefined
|
|
37
|
+
export let neutralMin: number | string | undefined = 0
|
|
38
|
+
export let neutralMax: number | string | undefined = 0
|
|
39
|
+
export let chip: boolean | string | undefined = undefined
|
|
40
|
+
export let sparkWidth: number | string | undefined = undefined
|
|
41
|
+
export let sparkHeight: number | string | undefined = undefined
|
|
42
|
+
export let sparkColor: string | undefined = undefined
|
|
43
|
+
export let sparkX: string | undefined = undefined
|
|
44
|
+
export let sparkY: string | undefined = undefined
|
|
45
|
+
export let sparkYScale: boolean | string | undefined = undefined
|
|
46
|
+
export let barColor: string | undefined = '#a5cdee'
|
|
47
|
+
export let negativeBarColor: string | undefined = '#fca5a5'
|
|
48
|
+
export let backgroundColor: string | undefined = 'transparent'
|
|
49
|
+
export let hideLabels: boolean | string | undefined = undefined
|
|
50
|
+
export let colGroup: string | undefined = undefined
|
|
51
|
+
export let fmtColumn: string | undefined = undefined
|
|
52
|
+
export let redNegatives: boolean | string | undefined = undefined
|
|
53
|
+
|
|
54
|
+
const {resolveColor, resolveColorPalette} = getThemeStores()
|
|
55
|
+
|
|
56
|
+
let barColorStore = resolveColor(barColor)
|
|
57
|
+
let negativeBarColorStore = resolveColor(negativeBarColor)
|
|
58
|
+
let backgroundColorStore = resolveColor(backgroundColor)
|
|
59
|
+
let colorScaleStore = resolveColorPalette(colorScale)
|
|
60
|
+
|
|
61
|
+
$: barColorStore = resolveColor(barColor)
|
|
62
|
+
$: negativeBarColorStore = resolveColor(negativeBarColor)
|
|
63
|
+
$: backgroundColorStore = resolveColor(backgroundColor)
|
|
64
|
+
$: colorScaleStore = resolveColorPalette(colorScale)
|
|
65
|
+
|
|
66
|
+
const props = getContext(propKey)
|
|
67
|
+
|
|
68
|
+
const identifier = Symbol('GrapheneColumn')
|
|
69
|
+
|
|
70
|
+
wrap = toBoolean(wrap) ?? false
|
|
71
|
+
wrapTitle = toBoolean(wrapTitle) ?? false
|
|
72
|
+
openInNewTab = toBoolean(openInNewTab) ?? false
|
|
73
|
+
downIsGood = toBoolean(downIsGood) ?? false
|
|
74
|
+
showValue = toBoolean(showValue) ?? true
|
|
75
|
+
deltaSymbol = toBoolean(deltaSymbol) ?? true
|
|
76
|
+
chip = toBoolean(chip) ?? false
|
|
77
|
+
sparkYScale = toBoolean(sparkYScale) ?? false
|
|
78
|
+
hideLabels = toBoolean(hideLabels) ?? false
|
|
79
|
+
redNegatives = toBoolean(redNegatives) ?? false
|
|
80
|
+
|
|
81
|
+
const coerceNumber = (value: number | string | undefined): number | undefined => {
|
|
82
|
+
if (value === undefined || value === null || value === '') return undefined
|
|
83
|
+
let parsed = Number(value)
|
|
84
|
+
return Number.isNaN(parsed) ? undefined : parsed
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const checkColumnName = () => {
|
|
88
|
+
try {
|
|
89
|
+
let data = $props.data?.[0]
|
|
90
|
+
if (!data || !Object.keys(data).includes(id)) {
|
|
91
|
+
let error = `Error in table: ${id} does not exist in the dataset`
|
|
92
|
+
if (strictBuild) throw new Error(error)
|
|
93
|
+
console.warn(error)
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (strictBuild) throw error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const options = () => ({
|
|
101
|
+
identifier,
|
|
102
|
+
id,
|
|
103
|
+
title,
|
|
104
|
+
align,
|
|
105
|
+
wrap,
|
|
106
|
+
wrapTitle,
|
|
107
|
+
contentType,
|
|
108
|
+
height,
|
|
109
|
+
width,
|
|
110
|
+
alt,
|
|
111
|
+
openInNewTab,
|
|
112
|
+
linkLabel,
|
|
113
|
+
fmt,
|
|
114
|
+
fmtColumn,
|
|
115
|
+
totalAgg,
|
|
116
|
+
totalFmt,
|
|
117
|
+
subtotalFmt,
|
|
118
|
+
weightCol,
|
|
119
|
+
downIsGood,
|
|
120
|
+
deltaSymbol,
|
|
121
|
+
chip,
|
|
122
|
+
neutralMin: coerceNumber(neutralMin) ?? 0,
|
|
123
|
+
neutralMax: coerceNumber(neutralMax) ?? 0,
|
|
124
|
+
showValue,
|
|
125
|
+
colorMax,
|
|
126
|
+
colorMin,
|
|
127
|
+
colorMid,
|
|
128
|
+
colorScale: $colorScaleStore,
|
|
129
|
+
colorBreakpoints,
|
|
130
|
+
scaleColumn,
|
|
131
|
+
colGroup,
|
|
132
|
+
description,
|
|
133
|
+
redNegatives,
|
|
134
|
+
sparkWidth,
|
|
135
|
+
sparkHeight,
|
|
136
|
+
sparkColor,
|
|
137
|
+
sparkX,
|
|
138
|
+
sparkY,
|
|
139
|
+
sparkYScale,
|
|
140
|
+
barColor: $barColorStore,
|
|
141
|
+
negativeBarColor: $negativeBarColorStore,
|
|
142
|
+
backgroundColor: $backgroundColorStore,
|
|
143
|
+
hideLabels,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const updateProps = () => {
|
|
147
|
+
checkColumnName()
|
|
148
|
+
props.update((state: any) => {
|
|
149
|
+
let next = {...state}
|
|
150
|
+
let existing = next.columns.findIndex((column: any) => column.identifier === identifier)
|
|
151
|
+
let option = options()
|
|
152
|
+
if (existing === -1) {
|
|
153
|
+
next.columns = [...next.columns, option]
|
|
154
|
+
} else {
|
|
155
|
+
next.columns = [
|
|
156
|
+
...next.columns.slice(0, existing),
|
|
157
|
+
option,
|
|
158
|
+
...next.columns.slice(existing + 1),
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
return next
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
$: updateProps()
|
|
166
|
+
|
|
167
|
+
onDestroy(() => {
|
|
168
|
+
props.update((state: any) => {
|
|
169
|
+
return {...state, columns: state.columns.filter((column: any) => column.identifier !== identifier)}
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
</script>
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {onMount} from 'svelte'
|
|
3
|
+
import {toBoolean} from '../component-utilities/inputUtils'
|
|
4
|
+
|
|
5
|
+
export let name: string
|
|
6
|
+
export let label: string | undefined = undefined
|
|
7
|
+
export let title: string | undefined = undefined
|
|
8
|
+
export let description: string | undefined = undefined
|
|
9
|
+
export let start: string | Date | undefined = undefined
|
|
10
|
+
export let end: string | Date | undefined = undefined
|
|
11
|
+
export let defaultValue: string | undefined = undefined
|
|
12
|
+
export let presetRanges: string | string[] | undefined = undefined
|
|
13
|
+
export let data: string | undefined = undefined
|
|
14
|
+
export let dates: string | undefined = undefined
|
|
15
|
+
export let hideDuringPrint: boolean | string = true
|
|
16
|
+
|
|
17
|
+
const DEFAULT_PRESETS = ['Last 7 Days', 'Last 30 Days', 'Last 90 Days', 'Last 365 Days', 'Last Month', 'Last Year', 'Month to Date', 'Month to Today', 'Year to Date', 'Year to Today', 'All Time']
|
|
18
|
+
|
|
19
|
+
let mounted = false
|
|
20
|
+
let queryKey = ''
|
|
21
|
+
let queryHandler: ((res: {rows?: any[]; error?: any}) => void) | null = null
|
|
22
|
+
|
|
23
|
+
let domainStart: string | null = null
|
|
24
|
+
let domainEnd: string | null = null
|
|
25
|
+
|
|
26
|
+
let currentStart: string | null = null
|
|
27
|
+
let currentEnd: string | null = null
|
|
28
|
+
let currentPreset: string = ''
|
|
29
|
+
let touched = false
|
|
30
|
+
|
|
31
|
+
$: hidePrint = toBoolean(hideDuringPrint)
|
|
32
|
+
$: presetList = (() => {
|
|
33
|
+
if (Array.isArray(presetRanges)) return presetRanges
|
|
34
|
+
if (presetRanges) return [presetRanges]
|
|
35
|
+
return DEFAULT_PRESETS
|
|
36
|
+
})()
|
|
37
|
+
$: displayLabel = title || label
|
|
38
|
+
|
|
39
|
+
onMount(() => {
|
|
40
|
+
mounted = true
|
|
41
|
+
currentStart = normalizeInput(start)
|
|
42
|
+
currentEnd = normalizeInput(end)
|
|
43
|
+
if (defaultValue && presetList.includes(defaultValue)) {
|
|
44
|
+
applyPreset(defaultValue, false)
|
|
45
|
+
} else {
|
|
46
|
+
updateParams()
|
|
47
|
+
}
|
|
48
|
+
refreshQuery()
|
|
49
|
+
return () => {
|
|
50
|
+
mounted = false
|
|
51
|
+
if (queryHandler) {
|
|
52
|
+
window.$GRAPHENE?.unsubscribe?.(queryHandler)
|
|
53
|
+
queryHandler = null
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
$: refreshQuery()
|
|
59
|
+
|
|
60
|
+
function refreshQuery () {
|
|
61
|
+
if (!mounted) return
|
|
62
|
+
let key = data && dates ? `${data}::${dates}` : ''
|
|
63
|
+
if (key === queryKey) return
|
|
64
|
+
if (queryHandler) {
|
|
65
|
+
window.$GRAPHENE?.unsubscribe?.(queryHandler)
|
|
66
|
+
queryHandler = null
|
|
67
|
+
}
|
|
68
|
+
queryKey = key
|
|
69
|
+
if (!data || !dates) return
|
|
70
|
+
let handler = (res: {rows?: any[]; error?: any}) => {
|
|
71
|
+
if (res.error || !res.rows?.length) return
|
|
72
|
+
let values = res.rows
|
|
73
|
+
.map(row => normalizeInput(row[dates]))
|
|
74
|
+
.filter((val): val is string => !!val)
|
|
75
|
+
if (!values.length) return
|
|
76
|
+
values.sort()
|
|
77
|
+
domainStart = values[0]
|
|
78
|
+
domainEnd = values[values.length - 1]
|
|
79
|
+
if (!touched) {
|
|
80
|
+
if (defaultValue && presetList.includes(defaultValue)) {
|
|
81
|
+
applyPreset(defaultValue, false)
|
|
82
|
+
} else {
|
|
83
|
+
let startCandidate = currentStart ?? domainStart
|
|
84
|
+
let endCandidate = currentEnd ?? (domainEnd ? addDaysString(domainEnd, 1) : null)
|
|
85
|
+
setRange(startCandidate, endCandidate, currentPreset, false)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (typeof window !== 'undefined' && window.$GRAPHENE?.query) {
|
|
90
|
+
window.$GRAPHENE.query(data, [dates], handler)
|
|
91
|
+
queryHandler = handler
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeInput (value: string | Date | null | undefined): string | null {
|
|
96
|
+
if (value === null || value === undefined) return null
|
|
97
|
+
if (value instanceof Date) return formatDate(value)
|
|
98
|
+
let parsed = new Date(value)
|
|
99
|
+
if (Number.isNaN(parsed.getTime())) return null
|
|
100
|
+
return formatDate(parsed)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function formatDate (value: Date): string {
|
|
104
|
+
let year = value.getFullYear()
|
|
105
|
+
let month = String(value.getMonth() + 1).padStart(2, '0')
|
|
106
|
+
let day = String(value.getDate()).padStart(2, '0')
|
|
107
|
+
return `${year}-${month}-${day}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function addDays (value: Date, days: number): Date {
|
|
111
|
+
let copy = new Date(value)
|
|
112
|
+
copy.setDate(copy.getDate() + days)
|
|
113
|
+
return copy
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function addDaysString (value: string, days: number): string {
|
|
117
|
+
let parsed = new Date(value)
|
|
118
|
+
if (Number.isNaN(parsed.getTime())) return value
|
|
119
|
+
return formatDate(addDays(parsed, days))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function startOfMonth (value: Date): Date {
|
|
123
|
+
return new Date(value.getFullYear(), value.getMonth(), 1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function startOfYear (value: Date): Date {
|
|
127
|
+
return new Date(value.getFullYear(), 0, 1)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function addMonths (value: Date, months: number): Date {
|
|
131
|
+
let copy = new Date(value)
|
|
132
|
+
copy.setMonth(copy.getMonth() + months)
|
|
133
|
+
return copy
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function addYears (value: Date, years: number): Date {
|
|
137
|
+
let copy = new Date(value)
|
|
138
|
+
copy.setFullYear(copy.getFullYear() + years)
|
|
139
|
+
return copy
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function setRange (startValue: string | null, endValue: string | null, preset: string, markTouched: boolean) {
|
|
143
|
+
currentStart = startValue
|
|
144
|
+
currentEnd = endValue
|
|
145
|
+
currentPreset = preset
|
|
146
|
+
if (markTouched) touched = true
|
|
147
|
+
updateParams()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function updateParams () {
|
|
151
|
+
window.$GRAPHENE.updateParam(`${name}_start`, currentStart)
|
|
152
|
+
window.$GRAPHENE.updateParam(`${name}_end`, currentEnd)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function onStartChange (event: Event) {
|
|
156
|
+
let value = (event.currentTarget as HTMLInputElement).value || null
|
|
157
|
+
setRange(value, currentEnd, '', true)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function onEndChange (event: Event) {
|
|
161
|
+
let value = (event.currentTarget as HTMLInputElement).value || null
|
|
162
|
+
setRange(currentStart, value, '', true)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function applyPreset (preset: string, markTouched = true) {
|
|
166
|
+
let baseEnd = (() => {
|
|
167
|
+
if (currentEnd) return new Date(currentEnd)
|
|
168
|
+
if (domainEnd) return new Date(domainEnd)
|
|
169
|
+
return new Date()
|
|
170
|
+
})()
|
|
171
|
+
if (Number.isNaN(baseEnd.getTime())) baseEnd = new Date()
|
|
172
|
+
let range = computePresetRange(preset, baseEnd)
|
|
173
|
+
if (!range) return
|
|
174
|
+
let startVal = range.start ? formatDate(range.start) : null
|
|
175
|
+
let endVal = range.end ? formatDate(range.end) : null
|
|
176
|
+
setRange(startVal, endVal, preset, markTouched)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function computePresetRange (preset: string, baseEndInclusive: Date): {start: Date | null; end: Date | null} | null {
|
|
180
|
+
let label = preset.trim()
|
|
181
|
+
let today = new Date()
|
|
182
|
+
let endExclusive = addDays(baseEndInclusive, 1)
|
|
183
|
+
|
|
184
|
+
let lastDaysMatch = label.match(/^Last (\d+) Days$/i)
|
|
185
|
+
if (lastDaysMatch) {
|
|
186
|
+
let days = parseInt(lastDaysMatch[1], 10)
|
|
187
|
+
let startDate = addDays(endExclusive, -days)
|
|
188
|
+
return {start: startDate, end: endExclusive}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let lastMonthsMatch = label.match(/^Last (\d+) Months$/i)
|
|
192
|
+
if (lastMonthsMatch) {
|
|
193
|
+
let months = parseInt(lastMonthsMatch[1], 10)
|
|
194
|
+
let monthEnd = startOfMonth(endExclusive)
|
|
195
|
+
let startDate = addMonths(monthEnd, -months)
|
|
196
|
+
return {start: startDate, end: monthEnd}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (label === 'Last Month') {
|
|
200
|
+
let monthEnd = startOfMonth(endExclusive)
|
|
201
|
+
let startDate = addMonths(monthEnd, -1)
|
|
202
|
+
return {start: startDate, end: monthEnd}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (label === 'Last Year') {
|
|
206
|
+
let yearEnd = startOfYear(endExclusive)
|
|
207
|
+
let startDate = addYears(yearEnd, -1)
|
|
208
|
+
return {start: startDate, end: yearEnd}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (label === 'Last 365 Days') {
|
|
212
|
+
let startDate = addDays(endExclusive, -365)
|
|
213
|
+
return {start: startDate, end: endExclusive}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (label === 'Month to Date') {
|
|
217
|
+
let startDate = startOfMonth(endExclusive)
|
|
218
|
+
return {start: startDate, end: endExclusive}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (label === 'Month to Today') {
|
|
222
|
+
let startDate = startOfMonth(today)
|
|
223
|
+
let endDate = addDays(today, 1)
|
|
224
|
+
return {start: startDate, end: endDate}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (label === 'Year to Date') {
|
|
228
|
+
let startDate = startOfYear(endExclusive)
|
|
229
|
+
return {start: startDate, end: endExclusive}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (label === 'Year to Today') {
|
|
233
|
+
let startDate = startOfYear(today)
|
|
234
|
+
let endDate = addDays(today, 1)
|
|
235
|
+
return {start: startDate, end: endDate}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (label === 'All Time') {
|
|
239
|
+
let startDate = domainStart ? new Date(domainStart) : null
|
|
240
|
+
let endDate = domainEnd ? addDays(new Date(domainEnd), 1) : endExclusive
|
|
241
|
+
return {start: startDate, end: endDate}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return null
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function onPresetChange (event: Event) {
|
|
248
|
+
let value = (event.currentTarget as HTMLSelectElement).value
|
|
249
|
+
if (!value) {
|
|
250
|
+
currentPreset = ''
|
|
251
|
+
touched = true
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
applyPreset(value, true)
|
|
255
|
+
}
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
<div class={`input-block${hidePrint ? ' hide-print' : ''}`}>
|
|
259
|
+
{#if displayLabel}
|
|
260
|
+
<label class="input-label" for={`daterange-${name}-start`}>{displayLabel}</label>
|
|
261
|
+
{/if}
|
|
262
|
+
{#if description}
|
|
263
|
+
<div class="input-description">{description}</div>
|
|
264
|
+
{/if}
|
|
265
|
+
<div class="range-row">
|
|
266
|
+
<input id={`daterange-${name}-start`} class="date-input" type="date" value={currentStart || ''} on:change={onStartChange} />
|
|
267
|
+
<span class="range-separator">to</span>
|
|
268
|
+
<input id={`daterange-${name}-end`} class="date-input" type="date" value={currentEnd || ''} on:change={onEndChange} />
|
|
269
|
+
</div>
|
|
270
|
+
{#if presetList.length}
|
|
271
|
+
<select class="preset-select" on:change={onPresetChange}>
|
|
272
|
+
<option value="">Custom range</option>
|
|
273
|
+
{#each presetList as preset (preset)}
|
|
274
|
+
<option value={preset} selected={preset === currentPreset}>{preset}</option>
|
|
275
|
+
{/each}
|
|
276
|
+
</select>
|
|
277
|
+
{/if}
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<style>
|
|
281
|
+
.input-block {
|
|
282
|
+
display: flex;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
gap: 6px;
|
|
285
|
+
margin: 8px 0;
|
|
286
|
+
}
|
|
287
|
+
@media print {
|
|
288
|
+
.hide-print {
|
|
289
|
+
display: none !important;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
.input-label {
|
|
293
|
+
font-size: 12px;
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
color: var(--input-label-color, #374151);
|
|
296
|
+
}
|
|
297
|
+
.input-description {
|
|
298
|
+
font-size: 12px;
|
|
299
|
+
color: rgba(55, 65, 81, 0.8);
|
|
300
|
+
}
|
|
301
|
+
.range-row {
|
|
302
|
+
display: flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: 8px;
|
|
305
|
+
}
|
|
306
|
+
.range-separator {
|
|
307
|
+
font-size: 12px;
|
|
308
|
+
color: rgba(55, 65, 81, 0.9);
|
|
309
|
+
}
|
|
310
|
+
.date-input {
|
|
311
|
+
padding: 6px 8px;
|
|
312
|
+
border-radius: 6px;
|
|
313
|
+
border: 1px solid rgba(107, 114, 128, 0.4);
|
|
314
|
+
font-size: 14px;
|
|
315
|
+
min-width: 150px;
|
|
316
|
+
}
|
|
317
|
+
.preset-select {
|
|
318
|
+
max-width: 220px;
|
|
319
|
+
padding: 6px 8px;
|
|
320
|
+
border-radius: 6px;
|
|
321
|
+
border: 1px solid rgba(107, 114, 128, 0.4);
|
|
322
|
+
font-size: 13px;
|
|
323
|
+
}
|
|
324
|
+
</style>
|