This commit is contained in:
2026-03-06 19:42:15 +00:00
parent abcbe3e1e5
commit 04cbb846d1
99 changed files with 11724 additions and 0 deletions

136
frontend/src/lib/api.ts Normal file
View File

@@ -0,0 +1,136 @@
const API_BASE = '/api'
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
...options,
})
if (!res.ok) {
const body = await res.json().catch(() => ({}))
throw new ApiError(res.status, body.error || res.statusText)
}
if (res.status === 204) return undefined as T
return res.json()
}
export class ApiError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
// Auth
export interface User {
id: string
email: string
display_name: string
preferred_locale: string
}
export const auth = {
register: (data: { email: string; password: string; display_name: string }) =>
request<User>('/auth/register', { method: 'POST', body: JSON.stringify(data) }),
login: (data: { email: string; password: string }) =>
request<User>('/auth/login', { method: 'POST', body: JSON.stringify(data) }),
logout: () => request<void>('/auth/logout', { method: 'POST' }),
me: () => request<User>('/auth/me'),
}
// Categories
export type CategoryType = 'bill' | 'variable_expense' | 'debt' | 'saving' | 'investment' | 'income'
export interface Category {
id: string
name: string
type: CategoryType
icon: string
sort_order: number
}
export const categories = {
list: () => request<Category[]>('/categories'),
create: (data: Partial<Category>) =>
request<Category>('/categories', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: Partial<Category>) =>
request<Category>(`/categories/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request<void>(`/categories/${id}`, { method: 'DELETE' }),
}
// Budgets
export interface Budget {
id: string
name: string
start_date: string
end_date: string
currency: string
carryover_amount: number
}
export interface BudgetItem {
id: string
budget_id: string
category_id: string
category_name: string
category_type: CategoryType
budgeted_amount: number
actual_amount: number
notes: string
}
export interface BudgetDetail extends Budget {
items: BudgetItem[]
totals: {
income_budget: number
income_actual: number
bills_budget: number
bills_actual: number
expenses_budget: number
expenses_actual: number
debts_budget: number
debts_actual: number
savings_budget: number
savings_actual: number
investments_budget: number
investments_actual: number
available: number
}
}
export const budgets = {
list: () => request<Budget[]>('/budgets'),
create: (data: Partial<Budget>) =>
request<Budget>('/budgets', { method: 'POST', body: JSON.stringify(data) }),
get: (id: string) => request<BudgetDetail>(`/budgets/${id}`),
update: (id: string, data: Partial<Budget>) =>
request<Budget>(`/budgets/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request<void>(`/budgets/${id}`, { method: 'DELETE' }),
copyFrom: (id: string, srcId: string) =>
request<BudgetDetail>(`/budgets/${id}/copy-from/${srcId}`, { method: 'POST' }),
}
// Budget Items
export const budgetItems = {
create: (budgetId: string, data: Partial<BudgetItem>) =>
request<BudgetItem>(`/budgets/${budgetId}/items`, { method: 'POST', body: JSON.stringify(data) }),
update: (budgetId: string, itemId: string, data: Partial<BudgetItem>) =>
request<BudgetItem>(`/budgets/${budgetId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (budgetId: string, itemId: string) =>
request<void>(`/budgets/${budgetId}/items/${itemId}`, { method: 'DELETE' }),
}
// Settings
export const settings = {
get: () => request<User>('/settings'),
update: (data: Partial<User>) =>
request<User>('/settings', { method: 'PUT', body: JSON.stringify(data) }),
}

View File

@@ -0,0 +1,6 @@
export function formatCurrency(amount: number, currency: string = 'EUR'): string {
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency,
}).format(amount)
}

View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}