| 1 | "use client"; |
1 | "use client"; |
| 2 | import Link from "next/link"; |
2 | import Link from "next/link"; |
| 3 | −import { useEffect, useMemo, useState, type FormEvent } from "react"; |
3 | +import { useEffect, useMemo, useRef, useState, type FormEvent } from "react"; |
| 4 | import { Download, Play } from "lucide-react"; |
4 | import { Download, Play } from "lucide-react"; |
| 5 | import { api, isApiError, type Bar, type BarsOut } from "@/lib/api"; |
5 | import { api, isApiError, type Bar, type BarsOut } from "@/lib/api"; |
| 6 | import { useQueryParam } from "@/lib/hooks"; |
6 | import { useQueryParam } from "@/lib/hooks"; |
| 15 | export default function BarsPage() { |
15 | export default function BarsPage() { |
| 16 | const qSymbol = useQueryParam("symbol"); |
16 | const qSymbol = useQueryParam("symbol"); |
| 17 | const qInterval = useQueryParam("interval"); |
17 | const qInterval = useQueryParam("interval"); |
| |
18 | + const qStart = useQueryParam("start"); |
| |
19 | + const qEnd = useQueryParam("end"); |
| 18 | const [symbol, setSymbol] = useState("AAPL"); |
20 | const [symbol, setSymbol] = useState("AAPL"); |
| 19 | const [interval, setInterval_] = useState<"1m" | "5m" | "1d">("1m"); |
21 | const [interval, setInterval_] = useState<"1m" | "5m" | "1d">("1m"); |
| 20 | const [start, setStart] = useState(daysAgo(7)); |
22 | const [start, setStart] = useState(daysAgo(7)); |
| 34 | useEffect(() => { |
36 | useEffect(() => { |
| 35 | if (qInterval === "1m" || qInterval === "5m" || qInterval === "1d") setInterval_(qInterval); |
37 | if (qInterval === "1m" || qInterval === "5m" || qInterval === "1d") setInterval_(qInterval); |
| 36 | }, [qInterval]); |
38 | }, [qInterval]); |
| |
39 | + useEffect(() => { |
| |
40 | + if (qStart && /^\d{4}-\d{2}-\d{2}$/.test(qStart)) setStart(qStart); |
| |
41 | + if (qEnd && /^\d{4}-\d{2}-\d{2}$/.test(qEnd)) setEnd(qEnd); |
| |
42 | + }, [qStart, qEnd]); |
| |
43 | + // deep link (/bars/?symbol=…&start=…&end=…) loads automatically once the params are applied |
| |
44 | + const autoRan = useRef(false); |
| |
45 | + useEffect(() => { |
| |
46 | + if (autoRan.current || !qSymbol || !qStart || !qEnd) return; |
| |
47 | + if (symbol === qSymbol.toUpperCase() && start === qStart && end === qEnd) { |
| |
48 | + autoRan.current = true; |
| |
49 | + void run(); |
| |
50 | + } |
| |
51 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| |
52 | + }, [symbol, start, end, qSymbol, qStart, qEnd]); |
| 37 | |
53 | |
| 38 | const spanDays = useMemo(() => Math.round((new Date(end).getTime() - new Date(start).getTime()) / 86_400_000), [start, end]); |
54 | const spanDays = useMemo(() => Math.round((new Date(end).getTime() - new Date(start).getTime()) / 86_400_000), [start, end]); |
| 39 | const tooLarge = spanDays > (MAX_SPAN[interval] ?? 31); |
55 | const tooLarge = spanDays > (MAX_SPAN[interval] ?? 31); |