diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json
index 23e3f97..23d06e0 100644
--- a/client/public/locales/en/common.json
+++ b/client/public/locales/en/common.json
@@ -41,7 +41,8 @@
"deleteError": "Error when deleting {{resource}} (status code: {{statusCode}})",
"editSuccess": "Successfully edited {{resource}}",
"editError": "Error when editing {{resource}} (status code: {{statusCode}})",
- "importProgress": "Importing: {{processed}}/{{total}}"
+ "importProgress": "Importing: {{processed}}/{{total}}",
+ "saveSuccessful": "Save successful!"
},
"kofi": "Tip me on Ko-fi",
"loading": "Loading",
@@ -252,6 +253,18 @@
"table": {
"actions": "Actions"
},
+ "settings": {
+ "header": "Settings",
+ "generic": {
+ "tab": "Generic",
+ "currency": {
+ "label": "Currency"
+ }
+ },
+ "extra_fields": {
+ "tab": "Extra Fields"
+ }
+ },
"documentTitle": {
"default": "Spoolman",
"suffix": " | Spoolman",
diff --git a/client/public/locales/sv/common.json b/client/public/locales/sv/common.json
index ba75663..a21903b 100644
--- a/client/public/locales/sv/common.json
+++ b/client/public/locales/sv/common.json
@@ -41,7 +41,8 @@
"deleteError": "Det gick inte att ta bort {{resource}} (statuskod: {{statusCode}})",
"editSuccess": "Lyckades ändra {{resource}}",
"editError": "Det gick inte att ändra {{resource}} (statuskod: {{statusCode}})",
- "importProgress": "Importerar: {{processed}}/{{total}}"
+ "importProgress": "Importerar: {{processed}}/{{total}}",
+ "saveSuccessful": "Sparning lyckades!"
},
"loading": "Laddar",
"version": "Version",
@@ -240,6 +241,18 @@
"table": {
"actions": "Åtgärder"
},
+ "settings": {
+ "header": "Inställningar",
+ "generic": {
+ "tab": "Allmänt",
+ "currency": {
+ "label": "Valuta"
+ }
+ },
+ "extra_fields": {
+ "tab": "Extra Fält"
+ }
+ },
"documentTitle": {
"default": "Spoolman",
"suffix": " | Spoolman",
diff --git a/client/src/App.tsx b/client/src/App.tsx
index bc2c03e..550fbcb 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -10,7 +10,14 @@ import dataProvider from "./components/dataProvider";
import { useTranslation } from "react-i18next";
import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom";
import { ColorModeContextProvider } from "./contexts/color-mode";
-import { FileOutlined, HighlightOutlined, HomeOutlined, QuestionOutlined, UserOutlined } from "@ant-design/icons";
+import {
+ FileOutlined,
+ HighlightOutlined,
+ HomeOutlined,
+ QuestionOutlined,
+ ToolOutlined,
+ UserOutlined,
+} from "@ant-design/icons";
import { ConfigProvider } from "antd";
import React from "react";
import { Locale } from "antd/es/locale";
@@ -139,6 +146,14 @@ function App() {
icon: ,
},
},
+ {
+ name: "settings",
+ list: "/settings",
+ meta: {
+ canDelete: false,
+ icon: ,
+ },
+ },
{
name: "help",
list: "/help",
@@ -202,6 +217,7 @@ function App() {
} />
} />
+ } />
} />
} />
diff --git a/client/src/pages/settings/ExtraFieldsSettings.tsx b/client/src/pages/settings/ExtraFieldsSettings.tsx
new file mode 100644
index 0000000..95bbec5
--- /dev/null
+++ b/client/src/pages/settings/ExtraFieldsSettings.tsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export function ExtraFieldsSettings() {
+ return <>Extra Fields Settings Content>;
+}
diff --git a/client/src/pages/settings/GenericSettings.tsx b/client/src/pages/settings/GenericSettings.tsx
new file mode 100644
index 0000000..f0e1adc
--- /dev/null
+++ b/client/src/pages/settings/GenericSettings.tsx
@@ -0,0 +1,79 @@
+import React from "react";
+import { useTranslate } from "@refinedev/core";
+import { Button, Form, Input, message } from "antd";
+import { useGetSettings, useSetSetting } from "./query";
+
+export function GenericSettings() {
+ const settings = useGetSettings();
+ const setSetting = useSetSetting();
+ const [form] = Form.useForm();
+ const [messageApi, contextHolder] = message.useMessage();
+ const t = useTranslate();
+
+ // Set initial form values
+ React.useEffect(() => {
+ if (settings.data) {
+ form.setFieldsValue({
+ currency: JSON.parse(settings.data.currency.value),
+ });
+ }
+ }, [settings.data, form]);
+
+ // Popup message if setSetting is successful
+ React.useEffect(() => {
+ if (setSetting.isSuccess) {
+ messageApi.success(t("notifications.saveSuccessful"));
+ }
+ }, [setSetting.isSuccess, messageApi, t]);
+
+ // Handle form submit
+ const onFinish = (values: { currency: string }) => {
+ // Check if the currency has changed
+ if (settings.data?.currency.value !== JSON.stringify(values.currency)) {
+ setSetting.mutate({
+ key: "currency",
+ value: JSON.stringify(values.currency),
+ });
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {contextHolder}
+ >
+ );
+}
diff --git a/client/src/pages/settings/index.tsx b/client/src/pages/settings/index.tsx
new file mode 100644
index 0000000..575f067
--- /dev/null
+++ b/client/src/pages/settings/index.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { IResourceComponentsProps, useTranslate } from "@refinedev/core";
+import dayjs from "dayjs";
+import utc from "dayjs/plugin/utc";
+import { Content } from "antd/es/layout/layout";
+import { Menu, theme } from "antd";
+import { SolutionOutlined, ToolOutlined } from "@ant-design/icons";
+import { useSavedState } from "../../utils/saveload";
+import { GenericSettings } from "./GenericSettings";
+import { ExtraFieldsSettings } from "./ExtraFieldsSettings";
+
+dayjs.extend(utc);
+
+const { useToken } = theme;
+
+export const Settings: React.FC = () => {
+ const { token } = useToken();
+ const t = useTranslate();
+ const [current, setCurrent] = useSavedState("settings-tab", "generic");
+
+ return (
+ <>
+
+ {t("settings.header")}
+
+
+
+ >
+ );
+};
+
+export default Settings;
diff --git a/client/src/pages/settings/query.ts b/client/src/pages/settings/query.ts
new file mode 100644
index 0000000..63ce94a
--- /dev/null
+++ b/client/src/pages/settings/query.ts
@@ -0,0 +1,55 @@
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+
+interface SettingResponseValue {
+ value: string;
+ is_set: boolean;
+ type: string;
+}
+
+interface SettingsResponse {
+ [key: string]: SettingResponseValue;
+}
+
+export function useGetSettings() {
+ const apiEndpoint = import.meta.env.VITE_APIURL;
+ return useQuery({
+ queryKey: ["settings"],
+ queryFn: async () => {
+ const response = await fetch(`${apiEndpoint}/setting`);
+ return response.json();
+ },
+ });
+}
+
+export function useGetSetting(key: string) {
+ const apiEndpoint = import.meta.env.VITE_APIURL;
+ return useQuery({
+ queryKey: ["settings", key],
+ queryFn: async () => {
+ const response = await fetch(`${apiEndpoint}/setting/${key}`);
+ return response.json();
+ },
+ });
+}
+
+export function useSetSetting() {
+ const queryClient = useQueryClient();
+
+ const apiEndpoint = import.meta.env.VITE_APIURL;
+ return useMutation({
+ mutationFn: async ({ key, value }) => {
+ const response = await fetch(`${apiEndpoint}/setting/${key}`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(value),
+ });
+ return response.json();
+ },
+ onSuccess: (_data, { key }) => {
+ // Invalidate and refetch
+ queryClient.invalidateQueries(["settings", key]);
+ },
+ });
+}