diff --git a/apps/backend/prisma/migrations/20260722152402_add_settings/migration.sql b/apps/backend/prisma/migrations/20260722152402_add_settings/migration.sql new file mode 100644 index 0000000..c3b1e96 --- /dev/null +++ b/apps/backend/prisma/migrations/20260722152402_add_settings/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "settings" ( + "key" TEXT NOT NULL, + "value" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "settings_pkey" PRIMARY KEY ("key") +); + +-- Seed +INSERT INTO "settings" ("key", "value", "createdAt", "updatedAt") +VALUES ('salary_multiplier', '4500', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 0096a3b..659ab5c 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -122,6 +122,15 @@ model QuoteHistory { @@map("quotes_history") } +model Setting { + key String @id + value String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("settings") +} + // Expenses enum PaymentStatus { diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index bc7bd65..0e750f3 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -11,6 +11,7 @@ import { startExpenseJob } from "./modules/expenses/expense.job"; import expensesRouter from "./modules/expenses/expenses.routes"; import { startQuoteJob } from "./modules/quotes/quote.job"; import quotesRouter from "./modules/quotes/quotes.routes"; +import settingsRouter from "./modules/settings/settings.routes"; import telegramRouter from "./modules/telegram/telegram.router"; import walletsRouter from "./modules/wallets/wallets.routes"; @@ -32,6 +33,7 @@ app.use("/api/*", async (c, next) => { }); app.route("/api/quotes", quotesRouter); +app.route("/api/settings", settingsRouter); app.route("/api", expensesRouter); app.route("/api/telegram", telegramRouter); app.route("/api/wallets", walletsRouter); diff --git a/apps/backend/src/modules/settings/settings.handler.ts b/apps/backend/src/modules/settings/settings.handler.ts new file mode 100644 index 0000000..0749650 --- /dev/null +++ b/apps/backend/src/modules/settings/settings.handler.ts @@ -0,0 +1,29 @@ +import type { Context } from "hono"; +import { cache } from "../../lib/cache"; +import { prisma } from "../../lib/prisma"; + +export async function getSettings(c: Context) { + const cached = cache.get("settings:all"); + if (cached) return c.json(cached); + + const settings = await prisma.setting.findMany(); + cache.set("settings:all", settings, 60_000); + return c.json(settings); +} + +export async function updateSetting(c: Context) { + const body = await c.req.json<{ key: string; value: string }>(); + + if (!body.key || body.value === undefined) { + return c.json({ error: "key and value are required" }, 400); + } + + const setting = await prisma.setting.upsert({ + where: { key: body.key }, + update: { value: body.value }, + create: { key: body.key, value: body.value }, + }); + + cache.invalidateByPrefix("settings:"); + return c.json(setting); +} diff --git a/apps/backend/src/modules/settings/settings.routes.ts b/apps/backend/src/modules/settings/settings.routes.ts new file mode 100644 index 0000000..2f72cdc --- /dev/null +++ b/apps/backend/src/modules/settings/settings.routes.ts @@ -0,0 +1,9 @@ +import { Hono } from "hono"; +import { getSettings, updateSetting } from "./settings.handler"; + +const app = new Hono(); + +app.get("/", getSettings); +app.put("/", updateSetting); + +export default app; diff --git a/apps/frontend/src/features/dashboard/DashboardPage.tsx b/apps/frontend/src/features/dashboard/DashboardPage.tsx index 6961e98..3957935 100644 --- a/apps/frontend/src/features/dashboard/DashboardPage.tsx +++ b/apps/frontend/src/features/dashboard/DashboardPage.tsx @@ -9,9 +9,9 @@ import { import type { Quote } from "@/lib/api"; import { useFetchQuotes, - useMonthlyPayedTotal, useMonthlyTotals, useQuotes, + useSettings, useWallets, } from "@/lib/queries"; import { RelativeTime } from "@/lib/time"; @@ -131,15 +131,15 @@ export function DashboardPage() { const bna = quotes?.find((q) => q.type === "BNA"); const now = new Date(); - const { data: monthlyExpenses } = useMonthlyPayedTotal( - now.getFullYear(), - now.getMonth() + 1, - ); const { data: monthlyTotals } = useMonthlyTotals( now.getFullYear(), now.getMonth() + 1, ); + const { data: settings } = useSettings(); + const salaryMultiplier = + settings?.find((s) => s.key === "salary_multiplier")?.value ?? "4500"; + const isFetching = isLoading || isPending; const { data: wallets } = useWallets(); @@ -173,9 +173,12 @@ export function DashboardPage() {
Gastos del mes
+Sueldo
- ${priceFormatter.format(monthlyExpenses?.total ?? 0)} + $ + {priceFormatter.format( + (belo ? Number(belo.buy) : 0) * Number(salaryMultiplier), + )}
{w.name}
-{w.currency}
++ {w.currency} +
{walletBalanceFormatter.format(w.balance)}
diff --git a/apps/frontend/src/lib/api.ts b/apps/frontend/src/lib/api.ts index 6af7cae..2b7efa2 100644 --- a/apps/frontend/src/lib/api.ts +++ b/apps/frontend/src/lib/api.ts @@ -339,7 +339,12 @@ export type Wallet = { export type WalletMovement = { id: number; walletId: number; - type: "DEPOSIT" | "WITHDRAWAL" | "TRANSFER_IN" | "TRANSFER_OUT" | "ADJUSTMENT"; + type: + | "DEPOSIT" + | "WITHDRAWAL" + | "TRANSFER_IN" + | "TRANSFER_OUT" + | "ADJUSTMENT"; amount: number; description: string | null; referenceWalletId: number | null; @@ -390,10 +395,7 @@ export function deleteWallet(id: number): Promise+ Valor para calcular el sueldo (Sueldo = BELO bid x este valor) +
+Cargando...
+ ) : ( ++ Multiplicador actualizado correctamente +
+ )} +