diff options
| author | Frank <[email protected]> | 2025-12-03 22:40:35 -0500 |
|---|---|---|
| committer | Frank <[email protected]> | 2025-12-03 22:41:31 -0500 |
| commit | 4ff5783e59cae829029f4363c60ab0ec03db0e60 (patch) | |
| tree | 0d505cac1a27ce776c52c0888eeca83388641dd6 | |
| parent | dcfeb5298323473e18f87be3c99a01e7ab29e7a7 (diff) | |
| download | opencode-4ff5783e59cae829029f4363c60ab0ec03db0e60.tar.gz opencode-4ff5783e59cae829029f4363c60ab0ec03db0e60.zip | |
zen: fix chart loading
| -rw-r--r-- | packages/console/app/src/routes/workspace/[id]/graph-section.tsx | 147 |
1 files changed, 69 insertions, 78 deletions
diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx b/packages/console/app/src/routes/workspace/[id]/graph-section.tsx index 46418d618..c8340286f 100644 --- a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/graph-section.tsx @@ -3,7 +3,7 @@ import { UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js" import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js" -import { createAsync, query, useParams } from "@solidjs/router" +import { useParams } from "@solidjs/router" import { createEffect, createMemo, onCleanup, Show, For } from "solid-js" import { createStore } from "solid-js/store" import { withActor } from "~/context/auth.withActor" @@ -94,8 +94,6 @@ async function getCosts(workspaceID: string, year: number, month: number) { }, workspaceID) } -const queryCosts = query(getCosts, "costs.get") - const MODEL_COLORS: Record<string, string> = { "claude-sonnet-4-5": "#D4745C", "claude-sonnet-4": "#E8B4A4", @@ -160,45 +158,25 @@ export function GraphSection() { keyDropdownOpen: false, colorScheme: "light" as "light" | "dark", }) - const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month)) - - createEffect(() => { - if (typeof window === "undefined") return - - const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") - setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" }) - - const handleColorSchemeChange = (e: MediaQueryListEvent) => { - setStore({ colorScheme: e.matches ? "dark" : "light" }) - } - - mediaQuery.addEventListener("change", handleColorSchemeChange) - onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange)) - }) - const onPreviousMonth = async () => { const month = store.month === 0 ? 11 : store.month - 1 const year = store.month === 0 ? store.year - 1 : store.year - const data = await getCosts(params.id!, year, month) - setStore({ month, year, data }) + setStore({ month, year }) } const onNextMonth = async () => { const month = store.month === 11 ? 0 : store.month + 1 const year = store.month === 11 ? store.year + 1 : store.year - setStore({ month, year, data: await getCosts(params.id!, year, month) }) + setStore({ month, year }) } const onSelectModel = (model: string | null) => setStore({ model, modelDropdownOpen: false }) const onSelectKey = (keyID: string | null) => setStore({ key: keyID, keyDropdownOpen: false }) - const getData = createMemo(() => store.data ?? initialData()) - const getModels = createMemo(() => { - const data = getData() - if (!data?.usage) return [] - return Array.from(new Set(data.usage.map((row) => row.model))).sort() + if (!store.data?.usage) return [] + return Array.from(new Set(store.data.usage.map((row) => row.model))).sort() }) const getDates = createMemo(() => { @@ -221,9 +199,7 @@ export function GraphSection() { const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth() const chartConfig = createMemo((): ChartConfiguration | null => { - if (typeof window === "undefined") return null - - const data = getData() + const data = store.data const dates = getDates() if (!data?.usage?.length) return null @@ -365,15 +341,32 @@ export function GraphSection() { } }) + createEffect(async () => { + const data = await getCosts(params.id!, store.year, store.month) + setStore({ data }) + }) + createEffect(() => { const config = chartConfig() if (!config || !canvasRef) return if (chartInstance) chartInstance.destroy() chartInstance = new Chart(canvasRef, config) + + onCleanup(() => chartInstance?.destroy()) }) - onCleanup(() => chartInstance?.destroy()) + createEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" }) + + const handleColorSchemeChange = (e: MediaQueryListEvent) => { + setStore({ colorScheme: e.matches ? "dark" : "light" }) + } + + mediaQuery.addEventListener("change", handleColorSchemeChange) + onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange)) + }) return ( <section class={styles.root}> @@ -382,55 +375,53 @@ export function GraphSection() { <p>Usage costs broken down by model.</p> </div> - <Show when={getData()}> - <div data-slot="filter-container"> - <div data-slot="month-picker"> - <button data-slot="month-button" onClick={onPreviousMonth}> - <IconChevronLeft /> + <div data-slot="filter-container"> + <div data-slot="month-picker"> + <button data-slot="month-button" onClick={onPreviousMonth}> + <IconChevronLeft /> + </button> + <span data-slot="month-label">{formatMonthYear()}</span> + <button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}> + <IconChevronRight /> + </button> + </div> + <Dropdown + trigger={store.model === null ? "All Models" : store.model} + open={store.modelDropdownOpen} + onOpenChange={(open) => setStore({ modelDropdownOpen: open })} + > + <> + <button data-slot="model-item" onClick={() => onSelectModel(null)}> + <span>All Models</span> </button> - <span data-slot="month-label">{formatMonthYear()}</span> - <button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}> - <IconChevronRight /> + <For each={getModels()}> + {(model) => ( + <button data-slot="model-item" onClick={() => onSelectModel(model)}> + <span>{model}</span> + </button> + )} + </For> + </> + </Dropdown> + <Dropdown + trigger={getKeyName(store.key)} + open={store.keyDropdownOpen} + onOpenChange={(open) => setStore({ keyDropdownOpen: open })} + > + <> + <button data-slot="model-item" onClick={() => onSelectKey(null)}> + <span>All Keys</span> </button> - </div> - <Dropdown - trigger={store.model === null ? "All Models" : store.model} - open={store.modelDropdownOpen} - onOpenChange={(open) => setStore({ modelDropdownOpen: open })} - > - <> - <button data-slot="model-item" onClick={() => onSelectModel(null)}> - <span>All Models</span> - </button> - <For each={getModels()}> - {(model) => ( - <button data-slot="model-item" onClick={() => onSelectModel(model)}> - <span>{model}</span> - </button> - )} - </For> - </> - </Dropdown> - <Dropdown - trigger={getKeyName(store.key)} - open={store.keyDropdownOpen} - onOpenChange={(open) => setStore({ keyDropdownOpen: open })} - > - <> - <button data-slot="model-item" onClick={() => onSelectKey(null)}> - <span>All Keys</span> - </button> - <For each={getData()?.keys || []}> - {(key) => ( - <button data-slot="model-item" onClick={() => onSelectKey(key.id)}> - <span>{key.displayName}</span> - </button> - )} - </For> - </> - </Dropdown> - </div> - </Show> + <For each={store.data?.keys || []}> + {(key) => ( + <button data-slot="model-item" onClick={() => onSelectKey(key.id)}> + <span>{key.displayName}</span> + </button> + )} + </For> + </> + </Dropdown> + </div> <Show when={chartConfig()} |
