aboutsummaryrefslogtreecommitdiff
path: root/server/frontend/src
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-01-22 01:10:10 +0100
committerLinnea Gräf <nea@nea.moe>2025-01-22 01:10:10 +0100
commit6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa (patch)
tree1a49a6aeb9e7f901ede729f1fed9d1d230dadc87 /server/frontend/src
parent550441921eed03b88ec94bea10deb1c45ef6e17b (diff)
downloadLocalTransactionLedger-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.tsx84
-rw-r--r--server/frontend/src/App.tsx17
-rw-r--r--server/frontend/src/api-schema.d.ts98
-rw-r--r--server/frontend/src/api.ts7
-rw-r--r--server/frontend/src/index.tsx2
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!);