diff options
author | Linnea Gräf <nea@nea.moe> | 2025-01-22 01:10:10 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2025-01-22 01:10:10 +0100 |
commit | 6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa (patch) | |
tree | 1a49a6aeb9e7f901ede729f1fed9d1d230dadc87 /server/frontend/src | |
parent | 550441921eed03b88ec94bea10deb1c45ef6e17b (diff) | |
download | LocalTransactionLedger-6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa.tar.gz LocalTransactionLedger-6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa.tar.bz2 LocalTransactionLedger-6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa.zip |
feat(server): Add first analysis
Diffstat (limited to 'server/frontend/src')
-rw-r--r-- | server/frontend/src/Analysis.tsx | 84 | ||||
-rw-r--r-- | server/frontend/src/App.tsx | 17 | ||||
-rw-r--r-- | server/frontend/src/api-schema.d.ts | 98 | ||||
-rw-r--r-- | server/frontend/src/api.ts | 7 | ||||
-rw-r--r-- | server/frontend/src/index.tsx | 2 |
5 files changed, 201 insertions, 7 deletions
diff --git a/server/frontend/src/Analysis.tsx b/server/frontend/src/Analysis.tsx new file mode 100644 index 0000000..3bf9c13 --- /dev/null +++ b/server/frontend/src/Analysis.tsx @@ -0,0 +1,84 @@ +import { createAsync, useParams } from "@solidjs/router" +import { client, getAnalysisList, paths } from "./api.ts"; +import { createSignal, For, onMount, Show, Suspense } from "solid-js"; +import { SolidApexCharts } from "solid-apexcharts"; + +type AnalysisResult = + { status: 'not requested' } + | { status: 'loading' } + | { status: 'loaded', result: paths['/analysis/execute']['get']['responses'][200]['content']['application/json'] } + +export default function Analysis() { + const pathParams = useParams(); + const analysisId = pathParams.id!; + let analysis = createAsync(() => getAnalysisList()); + const analysisName = () => analysis()?.data?.find(it => it.id == analysisId)?.name + const [startTimestamp, setStartTimestamp] = createSignal(new Date().getTime() - 1000 * 60 * 60 * 24 * 30); + const [endTimestamp, setEndTimestamp] = createSignal(new Date().getTime()); + const [analysisResult, setAnalysisResult] = createSignal<AnalysisResult>({ status: 'not requested' }); + return <> + <h1><Suspense fallback="Name not loaded...">{analysisName()}</Suspense></h1> + <p> + <label> + Start: + <input type="date" value={new Date(startTimestamp()).toISOString().substring(0, 10)} onInput={it => setStartTimestamp(it.target.valueAsNumber)}></input> + </label> + <label> + End: + <input type="date" value={new Date(endTimestamp()).toISOString().substring(0, 10)} onInput={it => setEndTimestamp(it.target.valueAsNumber)}></input> + </label> + <button disabled={analysisResult().status === 'loading'} onClick={() => { + setAnalysisResult({ status: 'loading' }); + (async () => { + const result = await client.GET('/analysis/execute', { + params: { + query: { + analysis: analysisId, + tEnd: endTimestamp(), + tStart: startTimestamp() + } + } + }); + setAnalysisResult({ + status: "loaded", + result: result.data! + }); + })(); + }}> + Refresh + </button> + + <Show when={takeIf(analysisResult(), it => it.status == 'loaded')}> + {element => + <For each={element().result.visualizations}> + {item => + <div> + <SolidApexCharts + width={1200} + type="bar" + options={{ + xaxis: { + type: 'numeric' + } + }} + series={[ + { + name: item.label, + data: item.dataPoints.map(it => ([it.time, it.value])) + } + ]} + ></SolidApexCharts> + </div> + } + </For>} + </Show> + </p > + </> +} + +function takeIf<T extends P, P>( + obj: P, + condition: (arg: P) => arg is T, +): T | false { + return condition(obj) ? obj : false; +}
\ No newline at end of file diff --git a/server/frontend/src/App.tsx b/server/frontend/src/App.tsx index e35bb42..bdc1007 100644 --- a/server/frontend/src/App.tsx +++ b/server/frontend/src/App.tsx @@ -1,11 +1,20 @@ -import type { Component } from "solid-js"; -import { A } from "@solidjs/router"; +import { For, Suspense, type Component } from "solid-js"; +import { A, createAsync } from "@solidjs/router"; +import { client, getAnalysisList } from "./api.ts"; const App: Component = () => { + let analysis = createAsync(() => getAnalysisList()); return ( <> - Hello World - <A href="/test">Test Page</A> + <Suspense fallback="Loading analysis..."> + <ul> + <For each={analysis()?.data}> + {item => + <li><A href={`/analysis/${item.id}`}>{item.name}</A></li> + } + </For> + </ul> + </Suspense> </> ); }; diff --git a/server/frontend/src/api-schema.d.ts b/server/frontend/src/api-schema.d.ts index eab0d7e..7ba1db4 100644 --- a/server/frontend/src/api-schema.d.ts +++ b/server/frontend/src/api-schema.d.ts @@ -21,6 +21,40 @@ export interface paths { patch?: never; trace?: never; }; + "/analysis/execute": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Execute an analysis on a given timeframe */ + get: operations["executeAnalysis"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/list": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all installed analysis */ + get: operations["getAnalysis"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/item": { parameters: { query?: never; @@ -89,10 +123,68 @@ export interface operations { }; }; }; + executeAnalysis: { + parameters: { + query: { + /** @description An analysis id obtained from getAnalysis */ + analysis: string; + /** @description The start of the timeframe to analyze */ + tStart: number; + /** @description The end of the timeframe to analyze. Make sure to use the end of the day if you want the entire day included. */ + tEnd: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + visualizations: { + label: string; + xLabel: string; + yLabel: string; + dataPoints: { + time: number; + value: number; + }[]; + }[]; + }; + }; + }; + }; + }; + getAnalysis: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + name: string; + id: string; + }[]; + }; + }; + }; + }; getItemNames: { parameters: { - query?: { - itemId?: string[]; + query: { + itemId: string[]; }; header?: never; path?: never; @@ -128,7 +220,7 @@ export interface operations { content: { "application/json": { /** @enum {string} */ - type: "ACCESSORIES_SWAPPING" | "ALLOWANCE_GAIN" | "AUCTION_BOUGHT" | "AUCTION_LISTING_CHARGE" | "AUCTION_SOLD" | "AUTOMERCHANT_PROFIT_COLLECT" | "BANK_DEPOSIT" | "BANK_WITHDRAW" | "BAZAAR_BUY_INSTANT" | "BAZAAR_BUY_ORDER" | "BAZAAR_SELL_INSTANT" | "BAZAAR_SELL_ORDER" | "BITS_PURSE_STATUS" | "BOOSTER_COOKIE_ATE" | "CAPSAICIN_EYEDROPS_USED" | "COMMUNITY_SHOP_BUY" | "CORPSE_DESECRATED" | "DIE_ROLLED" | "DRACONIC_SACRIFICE" | "DUNGEON_CHEST_OPEN" | "FORGED" | "GOD_POTION_DRANK" | "GOD_POTION_MIXIN_DRANK" | "GUMMY_POLAR_BEAR_ATE" | "KAT_TIMESKIP" | "KAT_UPGRADE" | "KISMET_REROLL" | "KUUDRA_CHEST_OPEN" | "NPC_BUY" | "NPC_SELL" | "PEST_REPELLENT_USED" | "VISITOR_BARGAIN" | "WYRM_EVOKED"; + type: "ACCESSORIES_SWAPPING" | "ALLOWANCE_GAIN" | "AUCTION_BOUGHT" | "AUCTION_LISTING_CHARGE" | "AUCTION_SOLD" | "AUTOMERCHANT_PROFIT_COLLECT" | "BANK_DEPOSIT" | "BANK_INTEREST" | "BANK_WITHDRAW" | "BASIC_REFORGE" | "BAZAAR_BUY_INSTANT" | "BAZAAR_BUY_ORDER" | "BAZAAR_SELL_INSTANT" | "BAZAAR_SELL_ORDER" | "BITS_PURSE_STATUS" | "BOOSTER_COOKIE_ATE" | "CAPSAICIN_EYEDROPS_USED" | "COMMUNITY_SHOP_BUY" | "CORPSE_DESECRATED" | "DIE_ROLLED" | "DRACONIC_SACRIFICE" | "DUNGEON_CHEST_OPEN" | "FORGED" | "GOD_POTION_DRANK" | "GOD_POTION_MIXIN_DRANK" | "GUMMY_POLAR_BEAR_ATE" | "KAT_TIMESKIP" | "KAT_UPGRADE" | "KISMET_REROLL" | "KUUDRA_CHEST_OPEN" | "NPC_BUY" | "NPC_SELL" | "PEST_REPELLENT_USED" | "VISITOR_BARGAIN" | "WYRM_EVOKED"; id: string; items: { itemId: string; diff --git a/server/frontend/src/api.ts b/server/frontend/src/api.ts index 22cf6ca..8ab6272 100644 --- a/server/frontend/src/api.ts +++ b/server/frontend/src/api.ts @@ -1,6 +1,13 @@ import createClient from "openapi-fetch"; import type { paths } from "./api-schema.js"; +import { query } from "@solidjs/router"; +export { type paths }; const apiRoot = import.meta.env.DEV ? "//localhost:8080/api" : "/api"; export const client = createClient<paths>({ baseUrl: apiRoot }); + +export const getAnalysisList = query( + () => client.GET("/analysis/list"), + "getAnalysisList" +) diff --git a/server/frontend/src/index.tsx b/server/frontend/src/index.tsx index 6ab7d4e..610a78b 100644 --- a/server/frontend/src/index.tsx +++ b/server/frontend/src/index.tsx @@ -1,5 +1,6 @@ /* @refresh reload */ import { render } from "solid-js/web"; +import 'solid-devtools'; import "./index.css"; import type { RouteDefinition } from "@solidjs/router"; @@ -16,6 +17,7 @@ if (!(root instanceof HTMLElement)) { const routes: Array<RouteDefinition> = [ { path: "/", component: lazy(() => import("./App.tsx")) }, { path: "/test/", component: lazy(() => import("./Test.tsx")) }, + { path: "/analysis/:id", component: lazy(() => import("./Analysis.tsx")) }, ]; render(() => <Router>{routes}</Router>, root!); |