SPB Git

spb/findata

Public

web: bars deep-link auto-load, curated GB precision

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 1, 2026) parent 9540256 Browse files

2 changed files +18 −2

modified apps/web/src/app/bars/page.tsx +17 −1
@@ -1,6 +1,6 @@
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,6 +15,8 @@ const MAX_SPAN: Record<string, number> = { "1m": 31, "5m": 186, "1d": 3660 };
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,6 +36,20 @@ export default function BarsPage() {
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);
modified apps/web/src/app/page.tsx +1 −1
@@ -75,7 +75,7 @@ export default function OverviewPage() {
75 <KpiTile 75 <KpiTile
76 label="Lake free" 76 label="Lake free"
77 value={lake ? fmtGB(lake.free_gb, 0) : "—"} 77 value={lake ? fmtGB(lake.free_gb, 0) : "—"}
78− sub={lake ? `curated ${fmtGB(lake.curated_gb, 0)} / budget ${lake.soft_limit_gb} GB · min free ${lake.hard_min_free_gb} GB` : undefined} 78+ sub={lake ? `curated ${fmtGB(lake.curated_gb, 2)} / budget ${lake.soft_limit_gb} GB · min free ${lake.hard_min_free_gb} GB` : undefined}
79 tone={freeTone} 79 tone={freeTone}
80 href={isOwner ? "/storage/" : undefined} 80 href={isOwner ? "/storage/" : undefined}
81 /> 81 />
82 82
Menu

Theme