export type Quote = { id: number; timeStamp: string; buy: string; sell: string; type: "BELO" | "BLUE" | "BNA"; difference: string; percentage: string; previousDayDifference: string; previousDayPercentage: string; }; export type FetchResult = { type: string; status: string; error?: string; }; export type HistoryQuote = { id: number; timeStamp: string; buy: string; sell: string; type: "BELO" | "BLUE" | "BNA"; }; export type DailyMinMax = { date: string; minBuy: number; maxBuy: number; minSell: number; maxSell: number; }; export type DailyQuote = { date: string; buy: number; sell: number; }; export type HistoricalMinMax = { minBuy: number; maxBuy: number; minBuyDate: string | null; maxBuyDate: string | null; }; async function fetcher(url: string): Promise { const res = await fetch(url, { credentials: "include" }); if (!res.ok) { throw new Error(`API error: ${res.status} ${res.statusText}`); } return res.json(); } async function mutator( url: string, method: string, body?: unknown, ): Promise { const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: body ? JSON.stringify(body) : undefined, credentials: "include", }); if (!res.ok) { throw new Error(`API error: ${res.status} ${res.statusText}`); } return res.json(); } export function getQuotes(): Promise { return fetcher("/api/quotes"); } export function fetchQuotes(): Promise { return fetcher("/api/quotes/fetch"); } export function getQuoteHistory( type: string, startDate: string, endDate: string, ): Promise { const params = new URLSearchParams({ startDate, endDate }); return fetcher(`/api/quotes/${type}/history?${params}`); } export function getDailyMinMax( type: string, startDate: string, endDate: string, ): Promise { const params = new URLSearchParams({ startDate, endDate }); return fetcher(`/api/quotes/${type}/min-max?${params}`); } export function getDailyQuotes( type: string, startDate: string, endDate: string, ): Promise { const params = new URLSearchParams({ startDate, endDate }); return fetcher(`/api/quotes/${type}/daily?${params}`); } export function getHistoricalMinMax(type: string): Promise { return fetcher(`/api/quotes/${type}/historical/min-max`); } // Expenses export type PeriodicExpense = { id: number; description: string; defaultDueDay: number; defaultAmount: string; periods: number[]; isDeleted: boolean; createdAt: string; currentMonthApplicable?: boolean; }; export type Expense = { id: number; description: string; periodicExpenseId: number | null; year: number; month: number; amount: string; amountPayed: string | null; beloPrice: string | null; usdcEquivalent: string | null; status: "PENDING" | "PAYED"; dueDate: string; paymentDate: string | null; periodicExpense?: PeriodicExpense | null; }; export type CreatePeriodicExpenseInput = { description: string; defaultDueDay: number; defaultAmount: number; periods: number[]; }; export type CreateNonPeriodicExpenseInput = { description: string; amount: number; dueDate: string; }; export type PayExpenseInput = { amountPayed: number; paymentDate?: string; }; export type UpdateExpenseInput = { amount: number; dueDate: string; status?: "PENDING"; amountPayed?: number; paymentDate?: string; }; export function getPeriodicExpenses(): Promise { return fetcher("/api/periodic-expenses"); } export function createPeriodicExpense( data: CreatePeriodicExpenseInput, ): Promise { return mutator("/api/periodic-expenses", "POST", data); } export function updatePeriodicExpense( id: number, data: CreatePeriodicExpenseInput, ): Promise { return mutator(`/api/periodic-expenses/${id}`, "PUT", data); } export function deletePeriodicExpense(id: number): Promise { return mutator(`/api/periodic-expenses/${id}`, "DELETE"); } export function generateMonthlyExpense(id: number): Promise { return mutator( `/api/periodic-expenses/${id}/generate-month`, "POST", ); } export type MonthlyExpensesTotal = { total: number }; export function getMonthlyPayedTotal( year?: number, month?: number, ): Promise { const params = new URLSearchParams(); if (year) params.set("year", String(year)); if (month) params.set("month", String(month)); return fetcher(`/api/expenses/monthly-total?${params}`); } export type PaginatedResponse = { data: T[]; total: number; page: number; pageSize: number; }; export type MonthlyTotals = { payed: number; pending: number; }; export function getExpenses( status?: string, page?: number, pageSize?: number, periodicExpenseId?: number, search?: string, ): Promise> { const params = new URLSearchParams(); if (status) params.set("status", status); if (page) params.set("page", String(page)); if (pageSize) params.set("pageSize", String(pageSize)); if (periodicExpenseId) params.set("periodicExpenseId", String(periodicExpenseId)); if (search) params.set("search", search); const qs = params.toString(); return fetcher>( `/api/expenses${qs ? `?${qs}` : ""}`, ); } export type PendingUpcomingExpense = Expense & { dueType: "overdue" | "upcoming"; }; export function getPendingUpcomingExpenses(): Promise< PendingUpcomingExpense[] > { return fetcher("/api/expenses/pending-upcoming"); } export function getTotalPending(): Promise { return fetcher("/api/expenses/pending-total"); } export function getMonthlyTotals( year?: number, month?: number, ): Promise { const params = new URLSearchParams(); if (year) params.set("year", String(year)); if (month) params.set("month", String(month)); return fetcher(`/api/expenses/totals?${params}`); } export function createNonPeriodicExpense( data: CreateNonPeriodicExpenseInput, ): Promise { return mutator("/api/expenses", "POST", data); } export function payExpense( id: number, data: PayExpenseInput, ): Promise { return mutator(`/api/expenses/${id}/pay`, "PUT", data); } export function updateExpense( id: number, data: UpdateExpenseInput, ): Promise { return mutator(`/api/expenses/${id}`, "PUT", data); } export type ImportResult = { imported: number; skipped: number; skippedNoPeriods: number; skippedDuplicate: number; errors?: string[]; }; export type ImportExpensesResult = { imported: number; skipped: number; errors?: string[]; }; export function importExpenses(file: File): Promise { const formData = new FormData(); formData.append("file", file); return fetch("/api/expenses/import", { method: "POST", body: formData, credentials: "include", }).then((r) => { if (!r.ok) throw new Error(`API error: ${r.status} ${r.statusText}`); return r.json(); }); } export function importPeriodicExpenses(file: File): Promise { const formData = new FormData(); formData.append("file", file); return fetch("/api/periodic-expenses/import", { method: "POST", body: formData, credentials: "include", }).then((r) => { if (!r.ok) throw new Error(`API error: ${r.status} ${r.statusText}`); return r.json(); }); } // Telegram export type TelegramStatus = { botUsername: string | null; connected: boolean; }; export type TelegramCode = { code: string; botUsername: string | null; }; export function getTelegramStatus(): Promise { return fetcher("/api/telegram/status"); } export function generateTelegramCode(): Promise { return mutator("/api/telegram/code", "POST"); }