Skip to content

Commit 4ff5783

Browse files
committed
zen: fix chart loading
1 parent dcfeb52 commit 4ff5783

File tree

1 file changed

+69
-78
lines changed

1 file changed

+69
-78
lines changed

packages/console/app/src/routes/workspace/[id]/graph-section.tsx

Lines changed: 69 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
33
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
44
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
55
import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
6-
import { createAsync, query, useParams } from "@solidjs/router"
6+
import { useParams } from "@solidjs/router"
77
import { createEffect, createMemo, onCleanup, Show, For } from "solid-js"
88
import { createStore } from "solid-js/store"
99
import { withActor } from "~/context/auth.withActor"
@@ -94,8 +94,6 @@ async function getCosts(workspaceID: string, year: number, month: number) {
9494
}, workspaceID)
9595
}
9696

97-
const queryCosts = query(getCosts, "costs.get")
98-
9997
const MODEL_COLORS: Record<string, string> = {
10098
"claude-sonnet-4-5": "#D4745C",
10199
"claude-sonnet-4": "#E8B4A4",
@@ -160,45 +158,25 @@ export function GraphSection() {
160158
keyDropdownOpen: false,
161159
colorScheme: "light" as "light" | "dark",
162160
})
163-
const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month))
164-
165-
createEffect(() => {
166-
if (typeof window === "undefined") return
167-
168-
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
169-
setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" })
170-
171-
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
172-
setStore({ colorScheme: e.matches ? "dark" : "light" })
173-
}
174-
175-
mediaQuery.addEventListener("change", handleColorSchemeChange)
176-
onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange))
177-
})
178-
179161
const onPreviousMonth = async () => {
180162
const month = store.month === 0 ? 11 : store.month - 1
181163
const year = store.month === 0 ? store.year - 1 : store.year
182-
const data = await getCosts(params.id!, year, month)
183-
setStore({ month, year, data })
164+
setStore({ month, year })
184165
}
185166

186167
const onNextMonth = async () => {
187168
const month = store.month === 11 ? 0 : store.month + 1
188169
const year = store.month === 11 ? store.year + 1 : store.year
189-
setStore({ month, year, data: await getCosts(params.id!, year, month) })
170+
setStore({ month, year })
190171
}
191172

192173
const onSelectModel = (model: string | null) => setStore({ model, modelDropdownOpen: false })
193174

194175
const onSelectKey = (keyID: string | null) => setStore({ key: keyID, keyDropdownOpen: false })
195176

196-
const getData = createMemo(() => store.data ?? initialData())
197-
198177
const getModels = createMemo(() => {
199-
const data = getData()
200-
if (!data?.usage) return []
201-
return Array.from(new Set(data.usage.map((row) => row.model))).sort()
178+
if (!store.data?.usage) return []
179+
return Array.from(new Set(store.data.usage.map((row) => row.model))).sort()
202180
})
203181

204182
const getDates = createMemo(() => {
@@ -221,9 +199,7 @@ export function GraphSection() {
221199
const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
222200

223201
const chartConfig = createMemo((): ChartConfiguration | null => {
224-
if (typeof window === "undefined") return null
225-
226-
const data = getData()
202+
const data = store.data
227203
const dates = getDates()
228204
if (!data?.usage?.length) return null
229205

@@ -365,15 +341,32 @@ export function GraphSection() {
365341
}
366342
})
367343

344+
createEffect(async () => {
345+
const data = await getCosts(params.id!, store.year, store.month)
346+
setStore({ data })
347+
})
348+
368349
createEffect(() => {
369350
const config = chartConfig()
370351
if (!config || !canvasRef) return
371352

372353
if (chartInstance) chartInstance.destroy()
373354
chartInstance = new Chart(canvasRef, config)
355+
356+
onCleanup(() => chartInstance?.destroy())
374357
})
375358

376-
onCleanup(() => chartInstance?.destroy())
359+
createEffect(() => {
360+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
361+
setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" })
362+
363+
const handleColorSchemeChange = (e: MediaQueryListEvent) => {
364+
setStore({ colorScheme: e.matches ? "dark" : "light" })
365+
}
366+
367+
mediaQuery.addEventListener("change", handleColorSchemeChange)
368+
onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange))
369+
})
377370

378371
return (
379372
<section class={styles.root}>
@@ -382,55 +375,53 @@ export function GraphSection() {
382375
<p>Usage costs broken down by model.</p>
383376
</div>
384377

385-
<Show when={getData()}>
386-
<div data-slot="filter-container">
387-
<div data-slot="month-picker">
388-
<button data-slot="month-button" onClick={onPreviousMonth}>
389-
<IconChevronLeft />
378+
<div data-slot="filter-container">
379+
<div data-slot="month-picker">
380+
<button data-slot="month-button" onClick={onPreviousMonth}>
381+
<IconChevronLeft />
382+
</button>
383+
<span data-slot="month-label">{formatMonthYear()}</span>
384+
<button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
385+
<IconChevronRight />
386+
</button>
387+
</div>
388+
<Dropdown
389+
trigger={store.model === null ? "All Models" : store.model}
390+
open={store.modelDropdownOpen}
391+
onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
392+
>
393+
<>
394+
<button data-slot="model-item" onClick={() => onSelectModel(null)}>
395+
<span>All Models</span>
390396
</button>
391-
<span data-slot="month-label">{formatMonthYear()}</span>
392-
<button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
393-
<IconChevronRight />
397+
<For each={getModels()}>
398+
{(model) => (
399+
<button data-slot="model-item" onClick={() => onSelectModel(model)}>
400+
<span>{model}</span>
401+
</button>
402+
)}
403+
</For>
404+
</>
405+
</Dropdown>
406+
<Dropdown
407+
trigger={getKeyName(store.key)}
408+
open={store.keyDropdownOpen}
409+
onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
410+
>
411+
<>
412+
<button data-slot="model-item" onClick={() => onSelectKey(null)}>
413+
<span>All Keys</span>
394414
</button>
395-
</div>
396-
<Dropdown
397-
trigger={store.model === null ? "All Models" : store.model}
398-
open={store.modelDropdownOpen}
399-
onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
400-
>
401-
<>
402-
<button data-slot="model-item" onClick={() => onSelectModel(null)}>
403-
<span>All Models</span>
404-
</button>
405-
<For each={getModels()}>
406-
{(model) => (
407-
<button data-slot="model-item" onClick={() => onSelectModel(model)}>
408-
<span>{model}</span>
409-
</button>
410-
)}
411-
</For>
412-
</>
413-
</Dropdown>
414-
<Dropdown
415-
trigger={getKeyName(store.key)}
416-
open={store.keyDropdownOpen}
417-
onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
418-
>
419-
<>
420-
<button data-slot="model-item" onClick={() => onSelectKey(null)}>
421-
<span>All Keys</span>
422-
</button>
423-
<For each={getData()?.keys || []}>
424-
{(key) => (
425-
<button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
426-
<span>{key.displayName}</span>
427-
</button>
428-
)}
429-
</For>
430-
</>
431-
</Dropdown>
432-
</div>
433-
</Show>
415+
<For each={store.data?.keys || []}>
416+
{(key) => (
417+
<button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
418+
<span>{key.displayName}</span>
419+
</button>
420+
)}
421+
</For>
422+
</>
423+
</Dropdown>
424+
</div>
434425

435426
<Show
436427
when={chartConfig()}

0 commit comments

Comments
 (0)