Frank 2 месяцев назад
Родитель
Сommit
4ff5783e59
1 измененных файлов с 69 добавлено и 78 удалено
  1. 69 78
      packages/console/app/src/routes/workspace/[id]/graph-section.tsx

+ 69 - 78
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()}