Compare commits
6 Commits
112d509274
...
8ed1974683
Author | SHA1 | Date | |
---|---|---|---|
8ed1974683 | |||
9e1b0a0287 | |||
7fabc142bc | |||
b5fee6dcbc | |||
c663a0e617 | |||
bbe5eb813f |
21
package-lock.json
generated
21
package-lock.json
generated
|
@ -7,6 +7,9 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "fin-check-front",
|
"name": "fin-check-front",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"chart.js": "^4.4.6"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
|
@ -511,6 +514,12 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
@ -1213,6 +1222,18 @@
|
||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "4.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
|
||||||
|
"integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
"tailwindcss": "^3.4.9",
|
"tailwindcss": "^3.4.9",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vite": "^5.0.3"
|
"vite": "^5.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"chart.js": "^4.4.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,6 +137,17 @@ export interface Currency {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StatsType {
|
||||||
|
value: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatsTypeCurrencyChart {
|
||||||
|
label: string;
|
||||||
|
elements: StatsType[];
|
||||||
|
}
|
||||||
|
|
||||||
export const EntityTypes = {
|
export const EntityTypes = {
|
||||||
card: "Card",
|
card: "Card",
|
||||||
type: "Type",
|
type: "Type",
|
||||||
|
@ -148,6 +159,9 @@ export const EntityTypes = {
|
||||||
metric: "Metric",
|
metric: "Metric",
|
||||||
currency: "Currency",
|
currency: "Currency",
|
||||||
expense_bulk: "ExpenseBulk",
|
expense_bulk: "ExpenseBulk",
|
||||||
|
// I don't know if I should put Statistics interfaces to Entities
|
||||||
|
stats_type: "StatsType",
|
||||||
|
stats_type_currency_chart: "StatsTypeCurrencyChart",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type EntityName = keyof typeof EntityTypes;
|
export type EntityName = keyof typeof EntityTypes;
|
||||||
|
@ -162,6 +176,8 @@ export type EntityType<T extends EntityName> =
|
||||||
T extends "metric" ? Metric :
|
T extends "metric" ? Metric :
|
||||||
T extends "currency" ? Currency :
|
T extends "currency" ? Currency :
|
||||||
T extends "expense_bulk" ? ExpenseBulk :
|
T extends "expense_bulk" ? ExpenseBulk :
|
||||||
|
T extends "stats_type" ? StatsType :
|
||||||
|
T extends "stats_type_currency_chart" ? StatsTypeCurrencyChart :
|
||||||
never;
|
never;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -357,3 +373,31 @@ export async function filter<F, R>(groupName: string, data: F, session?: string)
|
||||||
return { message: error.message };
|
return { message: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function get_stats_for<R>(groupName: string, session?: string): Promise<R | ErrorMessage> {
|
||||||
|
const url = `${BASE_API_URL}/statistics/${groupName}`
|
||||||
|
const defaultHeaders = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
const headers = session
|
||||||
|
? { ...defaultHeaders, Cookie: `session=${session}` }
|
||||||
|
: defaultHeaders
|
||||||
|
|
||||||
|
const config: RequestInit = {
|
||||||
|
method: 'GET',
|
||||||
|
headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, config);
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.json()
|
||||||
|
throw new Error(`Failed to update ${groupName}: ${body.message}`);
|
||||||
|
}
|
||||||
|
return await response.json() as R;
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error
|
||||||
|
return { message: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,85 @@
|
||||||
<p>Hello, World!</p>
|
<script lang="ts">
|
||||||
|
import { onMount, onDestroy } from "svelte";
|
||||||
|
import {
|
||||||
|
Chart,
|
||||||
|
DoughnutController,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
type ChartConfiguration,
|
||||||
|
} from "chart.js";
|
||||||
|
import type { StatsTypeCurrencyChart } from "$lib/entities";
|
||||||
|
|
||||||
|
Chart.register(DoughnutController, ArcElement, Tooltip, Legend);
|
||||||
|
|
||||||
|
let error: string | null = null;
|
||||||
|
let data: StatsTypeCurrencyChart[] = [];
|
||||||
|
let configs: ChartConfiguration[] = [];
|
||||||
|
let charts: Chart[] = [];
|
||||||
|
let canvases: HTMLCanvasElement[] = [];
|
||||||
|
|
||||||
|
async function fetchChartStats() {
|
||||||
|
try {
|
||||||
|
const result = await fetch("/api/statistics/type");
|
||||||
|
if (!result.ok) {
|
||||||
|
const obj = await result.json();
|
||||||
|
error = obj.message;
|
||||||
|
} else {
|
||||||
|
data = await result.json();
|
||||||
|
|
||||||
|
configs = data.map((chart) => ({
|
||||||
|
type: "doughnut",
|
||||||
|
data: {
|
||||||
|
labels: chart.elements.map((type) => type.name),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Chart Dataset",
|
||||||
|
data: chart.elements.map((type) => type.value / 100),
|
||||||
|
backgroundColor: chart.elements.map((type) => type.color),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error fetching data:", e);
|
||||||
|
error = "Failed to fetch chart data.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTypeCharts() {
|
||||||
|
charts.forEach((chart) => chart.destroy());
|
||||||
|
charts = configs.map((config, index) => new Chart(canvases[index], config));
|
||||||
|
}
|
||||||
|
onMount(async () => {
|
||||||
|
await fetchChartStats();
|
||||||
|
createTypeCharts();
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
charts.forEach((chart) => chart.destroy());
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<p class="bg-red-100 text-red-700 p-4 rounded">{error}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Render canvas elements for charts -->
|
||||||
|
<div class="w-1/2 grid grid-cols-2 gap-4">
|
||||||
|
{#each configs as _, index}
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="type-chart">
|
||||||
|
<canvas bind:this={canvases[index]}></canvas>
|
||||||
|
<span class="flex justify-center">{data[index].label}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.type-chart {
|
||||||
|
width: 20vw;
|
||||||
|
height: 20vw;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
27
src/routes/api/statistics/type/+server.ts
Normal file
27
src/routes/api/statistics/type/+server.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import type { ErrorMessage } from "$lib/api";
|
||||||
|
import { get_stats_for, type StatsTypeCurrencyChart } from "$lib/entities";
|
||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
|
function isErrorMessage(value: any): value is ErrorMessage {
|
||||||
|
return value && typeof value.message === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ cookies }): Promise<Response> => {
|
||||||
|
const session = cookies.get('session');
|
||||||
|
|
||||||
|
// const queryParams = url.searchParams.toString();
|
||||||
|
// Check if the entity is valid
|
||||||
|
if (!session) {
|
||||||
|
return new Response(JSON.stringify("no cookies"), { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScript type inference for entity
|
||||||
|
const result = await get_stats_for<StatsTypeCurrencyChart[]>("type", session);
|
||||||
|
|
||||||
|
if (isErrorMessage(result)) {
|
||||||
|
console.log("ERROR");
|
||||||
|
return new Response(JSON.stringify(result), { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(result), { status: 200 });
|
||||||
|
}
|
|
@ -117,11 +117,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get the name of the parent category
|
// Helper function to get the name of the parent category
|
||||||
function getCardName(cardId: number) {
|
|
||||||
if (cardId === 0) return "None";
|
|
||||||
const card = cards.find((card) => card.id === cardId);
|
|
||||||
return card ? card.name : "Unknown";
|
|
||||||
}
|
|
||||||
function getTypeColor(typeId: number) {
|
function getTypeColor(typeId: number) {
|
||||||
if (typeId === 0) return "None";
|
if (typeId === 0) return "None";
|
||||||
const type = types.find((card) => card.id === typeId);
|
const type = types.find((card) => card.id === typeId);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user