| 1 |
1 |
"use client"; |
| 2 |
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 |
4 |
import { Download, Play } from "lucide-react"; |
| 5 |
5 |
import { api, isApiError, type Bar, type BarsOut } from "@/lib/api"; |
| 6 |
6 |
import { useQueryParam } from "@/lib/hooks"; |
| 15 |
15 |
export default function BarsPage() { |
| 16 |
16 |
const qSymbol = useQueryParam("symbol"); |
| 17 |
17 |
const qInterval = useQueryParam("interval"); |
|
18 |
+ const qStart = useQueryParam("start"); |
|
19 |
+ const qEnd = useQueryParam("end"); |
| 18 |
20 |
const [symbol, setSymbol] = useState("AAPL"); |
| 19 |
21 |
const [interval, setInterval_] = useState<"1m" | "5m" | "1d">("1m"); |
| 20 |
22 |
const [start, setStart] = useState(daysAgo(7)); |
| 34 |
36 |
useEffect(() => { |
| 35 |
37 |
if (qInterval === "1m" || qInterval === "5m" || qInterval === "1d") setInterval_(qInterval); |
| 36 |
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 |
54 |
const spanDays = useMemo(() => Math.round((new Date(end).getTime() - new Date(start).getTime()) / 86_400_000), [start, end]); |
| 39 |
55 |
const tooLarge = spanDays > (MAX_SPAN[interval] ?? 31); |