"use client"; import { useEffect, useRef, useState, type KeyboardEvent } from "react"; import { Download, Play } from "lucide-react"; import clsx from "clsx"; import { api, type QueryOut } from "@/lib/api"; import { useAsync, useLocalStorage, useQueryParam } from "@/lib/hooks"; import { useToast } from "@/lib/toast"; import { downloadText, fmtInt, toCsv } from "@/lib/format"; import { Banner, Card, Empty, ErrorState, PageHeader } from "@/components/ui"; const DISPLAY_CAP = 1000; export default function PlaygroundPage() { const qSymbol = useQueryParam("symbol"); const [sql, setSql] = useLocalStorage("findata.playground.sql", "SELECT symbol_as_reported, count(*) bars FROM market_bars_stock_1m_raw GROUP BY 1 ORDER BY 2 DESC LIMIT 20"); const [maxRows, setMaxRows] = useState(1000); const [out, setOut] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const toast = useToast(); const ta = useRef(null); const views = useAsync((s) => api.get<{ views: { name: string; files: number }[]; examples: string[] }>("/query/views", undefined, s), []); useEffect(() => { if (qSymbol) setSql(`SELECT session_date, min(low) lo, max(high) hi, sum(volume) vol\nFROM market_bars_stock_1m_raw\nWHERE symbol_as_reported = '${qSymbol.toUpperCase().replace(/'/g, "")}'\nGROUP BY 1 ORDER BY 1 DESC LIMIT 100`); // eslint-disable-next-line react-hooks/exhaustive-deps }, [qSymbol]); async function run() { if (!sql.trim()) return; setBusy(true); setError(null); try { const o = await api.post("/query", { sql, max_rows: maxRows }); setOut(o); } catch (e) { setError(e instanceof Error ? e : new Error(String(e))); setOut(null); } finally { setBusy(false); } } function onKey(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); void run(); } if (e.key === "Tab") { e.preventDefault(); const el = e.currentTarget; const s = el.selectionStart, en = el.selectionEnd; const next = sql.slice(0, s) + " " + sql.slice(en); setSql(next); requestAnimationFrame(() => el.setSelectionRange(s + 2, s + 2)); } } const shown = out ? out.rows.slice(0, DISPLAY_CAP) : []; return ( <>