Skip to content

Commit ed589fa

Browse files
committed
Add base interaction
1 parent b67ece1 commit ed589fa

File tree

4 files changed

+274
-23
lines changed

4 files changed

+274
-23
lines changed

site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@types/color-convert": "2.0.0",
4949
"@types/lodash": "4.14.196",
5050
"@types/react-color": "3.0.6",
51+
"@types/react-date-range": "1.4.4",
5152
"@types/semver": "7.5.0",
5253
"@vitejs/plugin-react": "4.0.1",
5354
"@xstate/inspect": "0.8.0",
@@ -77,6 +78,7 @@
7778
"react-chartjs-2": "5.2.0",
7879
"react-color": "2.19.3",
7980
"react-confetti": "6.1.0",
81+
"react-date-range": "1.4.0",
8082
"react-dom": "18.2.0",
8183
"react-headless-tabs": "6.0.3",
8284
"react-helmet-async": "1.3.0",

site/pnpm-lock.yaml

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import Box from "@mui/material/Box"
2+
import { styled } from "@mui/material/styles"
3+
import { ComponentProps, useRef, useState } from "react"
4+
import "react-date-range/dist/styles.css"
5+
import "react-date-range/dist/theme/default.css"
6+
import Button from "@mui/material/Button"
7+
import ArrowRightAltOutlined from "@mui/icons-material/ArrowRightAltOutlined"
8+
import Popover from "@mui/material/Popover"
9+
import { DateRangePicker } from "react-date-range"
10+
import { format } from "date-fns"
11+
12+
export type DateRangeValue = {
13+
startDate: Date
14+
endDate: Date
15+
}
16+
17+
type RangesState = NonNullable<ComponentProps<typeof DateRangePicker>["ranges"]>
18+
19+
export const DateRange = ({
20+
value,
21+
onChange,
22+
}: {
23+
value: DateRangeValue
24+
onChange: (value: DateRangeValue) => void
25+
}) => {
26+
const selectionStatusRef = useRef<"idle" | "selecting">("idle")
27+
const anchorRef = useRef<HTMLButtonElement>(null)
28+
const [isOpen, setIsOpen] = useState(false)
29+
const [ranges, setRanges] = useState<RangesState>([
30+
{
31+
...value,
32+
key: "selection",
33+
},
34+
])
35+
const currentRange = {
36+
startDate: ranges[0].startDate as Date,
37+
endDate: ranges[0].endDate as Date,
38+
}
39+
const handleClose = () => {
40+
onChange({
41+
startDate: currentRange.startDate,
42+
endDate: currentRange.endDate,
43+
})
44+
setIsOpen(false)
45+
}
46+
47+
return (
48+
<>
49+
<Button ref={anchorRef} onClick={() => setIsOpen(true)}>
50+
<span>{format(currentRange.startDate, "MMM d, Y")}</span>
51+
<ArrowRightAltOutlined sx={{ width: 16, height: 16, mx: 1 }} />
52+
<span>{format(currentRange.endDate, "MMM d, Y")}</span>
53+
</Button>
54+
<Popover
55+
anchorEl={anchorRef.current}
56+
open={isOpen}
57+
onClose={handleClose}
58+
anchorOrigin={{
59+
vertical: "bottom",
60+
horizontal: "left",
61+
}}
62+
sx={{
63+
"& .MuiPaper-root": {
64+
marginTop: 1,
65+
},
66+
}}
67+
>
68+
<DateRangePickerWrapper
69+
component={DateRangePicker}
70+
onChange={(item) => {
71+
const range = item.selection
72+
setRanges([range])
73+
74+
// When it is the first selection, we don't want to close the popover
75+
// We have to do that ourselves because the library doesn't provide a way to do it
76+
if (selectionStatusRef.current === "idle") {
77+
selectionStatusRef.current = "selecting"
78+
return
79+
}
80+
81+
selectionStatusRef.current = "idle"
82+
const startDate = range.startDate as Date
83+
const endDate = range.endDate as Date
84+
onChange({
85+
startDate,
86+
endDate,
87+
})
88+
setIsOpen(false)
89+
}}
90+
moveRangeOnFirstSelection={false}
91+
months={2}
92+
ranges={ranges}
93+
maxDate={new Date()}
94+
direction="horizontal"
95+
/>
96+
</Popover>
97+
</>
98+
)
99+
}
100+
101+
const DateRangePickerWrapper: typeof Box = styled(Box)(({ theme }) => ({
102+
"& .rdrDefinedRangesWrapper": {
103+
background: theme.palette.background.paper,
104+
borderColor: theme.palette.divider,
105+
},
106+
107+
"& .rdrStaticRange": {
108+
background: theme.palette.background.paper,
109+
border: 0,
110+
fontSize: 14,
111+
color: theme.palette.text.secondary,
112+
113+
"&:hover .rdrStaticRangeLabel": {
114+
background: theme.palette.background.paperLight,
115+
color: theme.palette.text.primary,
116+
},
117+
118+
"&.rdrStaticRangeSelected": {
119+
color: `${theme.palette.text.primary} !important`,
120+
},
121+
},
122+
123+
"& .rdrInputRanges": {
124+
display: "none",
125+
},
126+
127+
"& .rdrDateDisplayWrapper": {
128+
backgroundColor: theme.palette.background.paper,
129+
},
130+
131+
"& .rdrCalendarWrapper": {
132+
backgroundColor: theme.palette.background.paperLight,
133+
},
134+
135+
"& .rdrDateDisplayItem": {
136+
background: "transparent",
137+
borderColor: theme.palette.divider,
138+
139+
"& input": {
140+
color: theme.palette.text.secondary,
141+
},
142+
143+
"&.rdrDateDisplayItemActive": {
144+
borderColor: theme.palette.text.primary,
145+
backgroundColor: theme.palette.background.paperLight,
146+
147+
"& input": {
148+
color: theme.palette.text.primary,
149+
},
150+
},
151+
},
152+
153+
"& .rdrMonthPicker select, & .rdrYearPicker select": {
154+
color: theme.palette.text.primary,
155+
appearance: "auto",
156+
background: "transparent",
157+
},
158+
159+
"& .rdrMonthName, & .rdrWeekDay": {
160+
color: theme.palette.text.secondary,
161+
},
162+
163+
"& .rdrDayPassive .rdrDayNumber span": {
164+
color: theme.palette.text.disabled,
165+
},
166+
167+
"& .rdrDayNumber span": {
168+
color: theme.palette.text.primary,
169+
},
170+
171+
"& .rdrDayToday .rdrDayNumber span": {
172+
fontWeight: 900,
173+
174+
"&:after": {
175+
display: "none",
176+
},
177+
},
178+
179+
"& .rdrInRange, & .rdrEndEdge, & .rdrStartEdge": {
180+
color: theme.palette.primary.main,
181+
},
182+
183+
"& .rdrDayDisabled": {
184+
backgroundColor: "transparent",
185+
186+
"& .rdrDayNumber span": {
187+
color: theme.palette.text.disabled,
188+
},
189+
},
190+
}))

site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,30 @@ import {
2323
TemplateInsightsResponse,
2424
UserLatencyInsightsResponse,
2525
} from "api/typesGenerated"
26-
import { ComponentProps } from "react"
26+
import { ComponentProps, ReactNode, useState } from "react"
2727
import { subDays, addHours, startOfHour } from "date-fns"
28+
import "react-date-range/dist/styles.css"
29+
import "react-date-range/dist/theme/default.css"
30+
import { DateRange, DateRangeValue } from "./DateRange"
2831

2932
export default function TemplateInsightsPage() {
3033
const now = new Date()
34+
const [dateRangeValue, setDateRangeValue] = useState<DateRangeValue>({
35+
startDate: subDays(now, 6),
36+
endDate: addHours(now, 1),
37+
})
3138
const { template } = useTemplateLayoutContext()
3239
const insightsFilter = {
3340
template_ids: template.id,
34-
start_time: toStartTimeFilter(subDays(now, 7)),
35-
end_time: startOfHour(addHours(now, 1)).toISOString(),
41+
start_time: toStartTimeFilter(dateRangeValue.startDate),
42+
end_time: startOfHour(dateRangeValue.endDate).toISOString(),
3643
}
3744
const { data: templateInsights } = useQuery({
38-
queryKey: ["templates", template.id, "usage"],
45+
queryKey: ["templates", template.id, "usage", insightsFilter],
3946
queryFn: () => getInsightsTemplate(insightsFilter),
4047
})
4148
const { data: userLatency } = useQuery({
42-
queryKey: ["templates", template.id, "user-latency"],
49+
queryKey: ["templates", template.id, "user-latency", insightsFilter],
4350
queryFn: () => getInsightsUserLatency(insightsFilter),
4451
})
4552

@@ -49,6 +56,9 @@ export default function TemplateInsightsPage() {
4956
<title>{getTemplatePageTitle("Insights", template)}</title>
5057
</Helmet>
5158
<TemplateInsightsPageView
59+
dateRangePicker={
60+
<DateRange value={dateRangeValue} onChange={setDateRangeValue} />
61+
}
5262
templateInsights={templateInsights}
5363
userLatency={userLatency}
5464
/>
@@ -59,29 +69,34 @@ export default function TemplateInsightsPage() {
5969
export const TemplateInsightsPageView = ({
6070
templateInsights,
6171
userLatency,
72+
dateRangePicker,
6273
}: {
6374
templateInsights: TemplateInsightsResponse | undefined
6475
userLatency: UserLatencyInsightsResponse | undefined
76+
dateRangePicker: ReactNode
6577
}) => {
6678
return (
67-
<Box
68-
sx={{
69-
display: "grid",
70-
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
71-
gridTemplateRows: "440px auto",
72-
gap: (theme) => theme.spacing(3),
73-
}}
74-
>
75-
<DailyUsersPanel
76-
sx={{ gridColumn: "span 2" }}
77-
data={templateInsights?.interval_reports}
78-
/>
79-
<UserLatencyPanel data={userLatency} />
80-
<TemplateUsagePanel
81-
sx={{ gridColumn: "span 3" }}
82-
data={templateInsights?.report.apps_usage}
83-
/>
84-
</Box>
79+
<>
80+
<Box sx={{ mb: 4 }}>{dateRangePicker}</Box>
81+
<Box
82+
sx={{
83+
display: "grid",
84+
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
85+
gridTemplateRows: "440px auto",
86+
gap: (theme) => theme.spacing(3),
87+
}}
88+
>
89+
<DailyUsersPanel
90+
sx={{ gridColumn: "span 2" }}
91+
data={templateInsights?.interval_reports}
92+
/>
93+
<UserLatencyPanel data={userLatency} />
94+
<TemplateUsagePanel
95+
sx={{ gridColumn: "span 3" }}
96+
data={templateInsights?.report.apps_usage}
97+
/>
98+
</Box>
99+
</>
85100
)
86101
}
87102

0 commit comments

Comments
 (0)