From 46bb66e8b489b3459f69b75bca90322561fa570a Mon Sep 17 00:00:00 2001
From: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com>
Date: Sat, 1 Feb 2025 21:30:31 +0800
Subject: [PATCH] Add `firefly iii` widget
Add new widget type, `firefly` which is implemented for `firefly iii` api,
to get some metadata about the account such as `net worth` and `budget` (first one)
Signed-off-by: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com>
---
public/locales/en/common.json | 4 ++
src/utils/proxy/handlers/credentialed.js | 1 +
src/widgets/components.js | 1 +
src/widgets/firefly/component.jsx | 71 ++++++++++++++++++++++++
src/widgets/firefly/widget.js | 19 +++++++
src/widgets/widgets.js | 2 +
6 files changed, 98 insertions(+)
create mode 100644 src/widgets/firefly/component.jsx
create mode 100644 src/widgets/firefly/widget.js
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 1d7380894..0674d6be3 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -702,6 +702,10 @@
"processed": "Processed",
"time": "Time"
},
+ "firefly": {
+ "networth": "Net Worth",
+ "budget": "Budget"
+ },
"grafana": {
"dashboards": "Dashboards",
"datasources": "Data Sources",
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 01fb313b1..9a333d6fb 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -48,6 +48,7 @@ export default async function credentialedProxyHandler(req, res, map) {
"tandoor",
"pterodactyl",
"vikunja",
+ "firefly",
].includes(widget.type)
) {
headers.Authorization = `Bearer ${widget.key}`;
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 19f41d4ae..67d17d50b 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -30,6 +30,7 @@ const components = {
esphome: dynamic(() => import("./esphome/component")),
evcc: dynamic(() => import("./evcc/component")),
fileflows: dynamic(() => import("./fileflows/component")),
+ firefly: dynamic(() => import("./firefly/component")),
flood: dynamic(() => import("./flood/component")),
freshrss: dynamic(() => import("./freshrss/component")),
frigate: dynamic(() => import("./frigate/component")),
diff --git a/src/widgets/firefly/component.jsx b/src/widgets/firefly/component.jsx
new file mode 100644
index 000000000..30e495c16
--- /dev/null
+++ b/src/widgets/firefly/component.jsx
@@ -0,0 +1,71 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+ const { widget } = service;
+
+ const startOfMonth = new Date();
+ startOfMonth.setDate(1);
+ startOfMonth.setHours(0, 0, 0, 0);
+ const startOfMonthFormatted = startOfMonth.toISOString().split("T")[0];
+
+ const endOfMonth = new Date(startOfMonth);
+ endOfMonth.setMonth(endOfMonth.getMonth() + 1);
+ endOfMonth.setDate(0);
+ endOfMonth.setHours(23, 59, 59, 999);
+ const endOfMonthFormatted = endOfMonth.toISOString().split("T")[0];
+
+ const { data: summaryData, error: summaryError } = useWidgetAPI(widget, "summary", {
+ start: startOfMonthFormatted,
+ end: endOfMonthFormatted,
+ });
+
+ const { data: budgetData, error: budgetError } = useWidgetAPI(widget, "budgets", {
+ start: startOfMonthFormatted,
+ end: endOfMonthFormatted,
+ });
+
+ if (summaryError || budgetError) {
+ return ;
+ }
+
+ if (!summaryData || !budgetData) {
+ return (
+
+
+
+
+ );
+ }
+
+ const netWorth = Object.keys(summaryData)
+ .filter((key) => key.includes("net-worth-in"))
+ .map((key) => summaryData[key]);
+
+ let budgetValue = null;
+
+ if (budgetData.data?.length && budgetData.data[0].type === "available_budgets") {
+ const budgetAmount = parseFloat(budgetData.data[0].attributes.amount);
+ const budgetSpent = -parseFloat(budgetData.data[0].attributes.spent_in_budgets[0]?.sum ?? "0");
+ const budgetCurrency = budgetData.data[0].attributes.currency_symbol;
+
+ budgetValue = `${budgetCurrency} ${t("common.number", {
+ value: budgetSpent,
+ minimumFractionDigits: 2,
+ })} / ${budgetCurrency} ${t("common.number", {
+ value: budgetAmount,
+ minimumFractionDigits: 2,
+ })}`;
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/widgets/firefly/widget.js b/src/widgets/firefly/widget.js
new file mode 100644
index 000000000..cd23504db
--- /dev/null
+++ b/src/widgets/firefly/widget.js
@@ -0,0 +1,19 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/api/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ summary: {
+ endpoint: "v1/summary/basic",
+ params: ["start", "end"],
+ },
+ budgets: {
+ endpoint: "v1/available-budgets",
+ params: ["start", "end"],
+ },
+ },
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 9d4bb935d..b78f5b9c2 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -24,6 +24,7 @@ import emby from "./emby/widget";
import esphome from "./esphome/widget";
import evcc from "./evcc/widget";
import fileflows from "./fileflows/widget";
+import firefly from "./firefly/widget";
import flood from "./flood/widget";
import freshrss from "./freshrss/widget";
import frigate from "./frigate/widget";
@@ -157,6 +158,7 @@ const widgets = {
esphome,
evcc,
fileflows,
+ firefly,
flood,
freshrss,
frigate,