diff --git a/frontend/components/Chart/Donut.vue b/frontend/components/Chart/Donut.vue
new file mode 100644
index 0000000..97d4113
--- /dev/null
+++ b/frontend/components/Chart/Donut.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
diff --git a/frontend/components/Chart/Line.vue b/frontend/components/Chart/Line.vue
new file mode 100644
index 0000000..014f20c
--- /dev/null
+++ b/frontend/components/Chart/Line.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
diff --git a/frontend/composables/use-css-var.ts b/frontend/composables/use-css-var.ts
new file mode 100644
index 0000000..c92e1e9
--- /dev/null
+++ b/frontend/composables/use-css-var.ts
@@ -0,0 +1,38 @@
+import { ComputedRef } from "vue";
+
+type ColorType = "hsla";
+
+export type VarOptions = {
+ type: ColorType;
+ transparency?: number;
+ apply?: (value: string) => string;
+};
+
+export function useCssVar(name: string, options?: VarOptions): ComputedRef {
+ if (!options) {
+ options = {
+ type: "hsla",
+ transparency: 1,
+ apply: null,
+ };
+ }
+
+ switch (options.type) {
+ case "hsla": {
+ return computed(() => {
+ if (!document) {
+ return "";
+ }
+
+ let val = getComputedStyle(document.documentElement).getPropertyValue(name);
+ val = val.trim().split(" ").join(", ");
+
+ if (options.transparency) {
+ val += `, ${options.transparency}`;
+ }
+
+ return `hsla(${val})`;
+ });
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 973e383..93ca2a2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,12 +36,14 @@
"@tailwindcss/typography": "^0.5.4",
"@vueuse/nuxt": "^9.1.1",
"autoprefixer": "^10.4.8",
+ "chart.js": "^4.0.1",
"daisyui": "^2.24.0",
"dompurify": "^2.4.1",
"markdown-it": "^13.0.1",
"pinia": "^2.0.21",
"postcss": "^8.4.16",
"tailwindcss": "^3.1.8",
- "vue": "^3.2.38"
+ "vue": "^3.2.38",
+ "vue-chartjs": "^4.1.2"
}
}
\ No newline at end of file
diff --git a/frontend/pages/home.vue b/frontend/pages/home.vue
index e8a52e7..ea93771 100644
--- a/frontend/pages/home.vue
+++ b/frontend/pages/home.vue
@@ -6,7 +6,6 @@
definePageMeta({
middleware: ["auth"],
});
-
useHead({
title: "Homebox | Home",
});
@@ -87,6 +86,63 @@
eventBus.emit(EventTypes.ClearStores);
}
+
+ const { data: timeseries } = useAsyncData(async () => {
+ const { data } = await api.stats.totalPriceOverTime();
+ return data;
+ });
+
+ const primary = useCssVar("--p");
+ const secondary = useCssVar("--s");
+ const accent = useCssVar("--a");
+ const neutral = useCssVar("--n");
+ const base = useCssVar("--b");
+
+ const chartData = computed(() => {
+ let start = timeseries.value?.valueAtStart;
+
+ return {
+ labels: timeseries?.value.entries.map(t => new Date(t.date).toDateString()) || [],
+ datasets: [
+ {
+ label: "Purchase Price",
+ data:
+ timeseries.value?.entries.map(t => {
+ start += t.value;
+ return start;
+ }) || [],
+ backgroundColor: primary.value,
+ borderColor: primary.value,
+ },
+ ],
+ };
+ });
+
+ const { data: donutSeries } = useAsyncData(async () => {
+ const { data } = await api.stats.locations();
+ return data;
+ });
+
+ const donutData = computed(() => {
+ return {
+ labels: donutSeries.value?.map(l => l.name) || [],
+ datasets: [
+ {
+ label: "Value",
+ data: donutSeries.value?.map(l => l.total) || [],
+ backgroundColor: [primary.value, secondary.value, neutral.value],
+ borderColor: [primary.value, secondary.value, neutral.value],
+ hoverOffset: 4,
+ },
+ ],
+ };
+ });
+
+ const refDonutEl = ref(null);
+
+ const donutElWidth = computed(() => {
+ return refDonutEl.value?.clientWidth || 0;
+ });
@@ -148,6 +204,25 @@
+
+
+ Total Asset Value {{ fmtCurrency(timeseries.valueAtEnd) }}
+
+
+
+
+
+
+
+ Asset By Location {{ fmtCurrency(timeseries.valueAtEnd) }}
+
+
+
+
+
+
+
+