Files
personal-admin-2026/apps/frontend/src/lib/api.ts
Jose Selesan 1d954217b7 feat(wallets): implement wallet management features including create, update, delete, deposit, transfer, and list movements
- Added createWallet, updateWallet, deleteWallet, depositWallet, transferWallet, and listMovements handlers.
- Created corresponding routes for wallet operations.
- Developed frontend components for wallet management including dialogs for creating, editing, depositing, adjusting balance, transferring, and viewing movements.
- Integrated wallet management into the authenticated routes.
2026-06-17 09:36:55 -03:00

437 lines
10 KiB
TypeScript

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<T>(url: string): Promise<T> {
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<T>(
url: string,
method: string,
body?: unknown,
): Promise<T> {
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<Quote[]> {
return fetcher<Quote[]>("/api/quotes");
}
export function fetchQuotes(): Promise<FetchResult[]> {
return fetcher<FetchResult[]>("/api/quotes/fetch");
}
export function getQuoteHistory(
type: string,
startDate: string,
endDate: string,
): Promise<HistoryQuote[]> {
const params = new URLSearchParams({ startDate, endDate });
return fetcher<HistoryQuote[]>(`/api/quotes/${type}/history?${params}`);
}
export function getDailyMinMax(
type: string,
startDate: string,
endDate: string,
): Promise<DailyMinMax[]> {
const params = new URLSearchParams({ startDate, endDate });
return fetcher<DailyMinMax[]>(`/api/quotes/${type}/min-max?${params}`);
}
export function getDailyQuotes(
type: string,
startDate: string,
endDate: string,
): Promise<DailyQuote[]> {
const params = new URLSearchParams({ startDate, endDate });
return fetcher<DailyQuote[]>(`/api/quotes/${type}/daily?${params}`);
}
export function getHistoricalMinMax(type: string): Promise<HistoricalMinMax> {
return fetcher<HistoricalMinMax>(`/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<PeriodicExpense[]> {
return fetcher<PeriodicExpense[]>("/api/periodic-expenses");
}
export function createPeriodicExpense(
data: CreatePeriodicExpenseInput,
): Promise<PeriodicExpense> {
return mutator<PeriodicExpense>("/api/periodic-expenses", "POST", data);
}
export function updatePeriodicExpense(
id: number,
data: CreatePeriodicExpenseInput,
): Promise<PeriodicExpense> {
return mutator<PeriodicExpense>(`/api/periodic-expenses/${id}`, "PUT", data);
}
export function deletePeriodicExpense(id: number): Promise<void> {
return mutator<void>(`/api/periodic-expenses/${id}`, "DELETE");
}
export function generateMonthlyExpense(id: number): Promise<Expense> {
return mutator<Expense>(
`/api/periodic-expenses/${id}/generate-month`,
"POST",
);
}
export type MonthlyExpensesTotal = { total: number };
export function getMonthlyPayedTotal(
year?: number,
month?: number,
): Promise<MonthlyExpensesTotal> {
const params = new URLSearchParams();
if (year) params.set("year", String(year));
if (month) params.set("month", String(month));
return fetcher<MonthlyExpensesTotal>(`/api/expenses/monthly-total?${params}`);
}
export type PaginatedResponse<T> = {
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<PaginatedResponse<Expense>> {
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<PaginatedResponse<Expense>>(
`/api/expenses${qs ? `?${qs}` : ""}`,
);
}
export type PendingUpcomingExpense = Expense & {
dueType: "overdue" | "upcoming";
};
export function getPendingUpcomingExpenses(): Promise<
PendingUpcomingExpense[]
> {
return fetcher<PendingUpcomingExpense[]>("/api/expenses/pending-upcoming");
}
export function getTotalPending(): Promise<MonthlyExpensesTotal> {
return fetcher<MonthlyExpensesTotal>("/api/expenses/pending-total");
}
export function getMonthlyTotals(
year?: number,
month?: number,
): Promise<MonthlyTotals> {
const params = new URLSearchParams();
if (year) params.set("year", String(year));
if (month) params.set("month", String(month));
return fetcher<MonthlyTotals>(`/api/expenses/totals?${params}`);
}
export function createNonPeriodicExpense(
data: CreateNonPeriodicExpenseInput,
): Promise<Expense> {
return mutator<Expense>("/api/expenses", "POST", data);
}
export function payExpense(
id: number,
data: PayExpenseInput,
): Promise<Expense> {
return mutator<Expense>(`/api/expenses/${id}/pay`, "PUT", data);
}
export function updateExpense(
id: number,
data: UpdateExpenseInput,
): Promise<Expense> {
return mutator<Expense>(`/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<ImportExpensesResult> {
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<ImportResult> {
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();
});
}
// Wallets
export type Wallet = {
id: number;
name: string;
currency: string;
balance: number;
createdAt: string;
updatedAt: string;
};
export type WalletMovement = {
id: number;
walletId: number;
type: "DEPOSIT" | "WITHDRAWAL" | "TRANSFER_IN" | "TRANSFER_OUT" | "ADJUSTMENT";
amount: number;
description: string | null;
referenceWalletId: number | null;
transferGroupId: string | null;
createdAt: string;
};
export type CreateWalletInput = {
name: string;
currency: string;
initialBalance?: number;
};
export type DepositInput = {
amount: number;
description?: string;
};
export type AdjustBalanceInput = {
newBalance: number;
description?: string;
};
export type TransferInput = {
fromWalletId: number;
toWalletId: number;
outgoingAmount: number;
incomingAmount: number;
description?: string;
};
export function getWallets(): Promise<Wallet[]> {
return fetcher<Wallet[]>("/api/wallets");
}
export function createWallet(data: CreateWalletInput): Promise<Wallet> {
return mutator<Wallet>("/api/wallets", "POST", data);
}
export function updateWallet(
id: number,
data: { name: string; currency: string },
): Promise<Wallet> {
return mutator<Wallet>(`/api/wallets/${id}`, "PUT", data);
}
export function deleteWallet(id: number): Promise<void> {
return mutator<void>(`/api/wallets/${id}`, "DELETE");
}
export function depositWallet(
id: number,
data: DepositInput,
): Promise<Wallet> {
return mutator<Wallet>(`/api/wallets/${id}/deposit`, "POST", data);
}
export function adjustBalance(
id: number,
data: AdjustBalanceInput,
): Promise<Wallet> {
return mutator<Wallet>(`/api/wallets/${id}/adjust`, "POST", data);
}
export function transferWallet(
data: TransferInput,
): Promise<{ from: Wallet; to: Wallet }> {
return mutator<{ from: Wallet; to: Wallet }>(
"/api/wallets/transfer",
"POST",
data,
);
}
export function getWalletMovements(
id: number,
): Promise<WalletMovement[]> {
return fetcher<WalletMovement[]>(`/api/wallets/${id}/movements`);
}
// Telegram
export type TelegramStatus = {
botUsername: string | null;
connected: boolean;
};
export type TelegramCode = {
code: string;
botUsername: string | null;
};
export function getTelegramStatus(): Promise<TelegramStatus> {
return fetcher<TelegramStatus>("/api/telegram/status");
}
export function generateTelegramCode(): Promise<TelegramCode> {
return mutator<TelegramCode>("/api/telegram/code", "POST");
}