zmiany
This commit is contained in:
27
web/src/app/core/services/budgets.service.ts
Normal file
27
web/src/app/core/services/budgets.service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import type { Budget, BudgetListResponse } from '../../shared/models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BudgetsService {
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
list(month?: string) {
|
||||
let params = new HttpParams();
|
||||
if (month) params = params.set('month', month);
|
||||
return this.http.get<BudgetListResponse>(`${environment.apiBaseUrl}/budgets`, { params });
|
||||
}
|
||||
|
||||
create(payload: { month: string; name?: string; amount: number; categoryId?: string | null; alertThresholds: number[]; isActive: boolean }) {
|
||||
return this.http.post<{ item: Budget }>(`${environment.apiBaseUrl}/budgets`, payload);
|
||||
}
|
||||
|
||||
update(id: string, payload: { month: string; name?: string; amount: number; categoryId?: string | null; alertThresholds: number[]; isActive: boolean }) {
|
||||
return this.http.put<{ item: Budget }>(`${environment.apiBaseUrl}/budgets/${id}`, payload);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.http.delete<void>(`${environment.apiBaseUrl}/budgets/${id}`);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,41 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import type { Expense, Proof } from '../../shared/models';
|
||||
import type { DuplicateGroup, Expense, Proof } from '../../shared/models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ExpensesService {
|
||||
private readonly http = inject(HttpClient);
|
||||
list(filters: { startDate?: string; endDate?: string; categoryId?: string; search?: string } = {}) { let params = new HttpParams(); Object.entries(filters).forEach(([key, value]) => { if (value) params = params.set(key, value); }); return this.http.get<{ items: Expense[] }>(`${environment.apiBaseUrl}/expenses`, { params }); }
|
||||
create(formData: FormData) { return this.http.post<{ item: Expense }>(`${environment.apiBaseUrl}/expenses`, formData); }
|
||||
update(id: string, payload: Partial<Expense> & { categoryId: string }) { return this.http.put<{ item: Expense }>(`${environment.apiBaseUrl}/expenses/${id}`, payload); }
|
||||
delete(id: string) { return this.http.delete<void>(`${environment.apiBaseUrl}/expenses/${id}`); }
|
||||
addProof(id: string, formData: FormData) { return this.http.post<{ proof: Proof; expense: Expense }>(`${environment.apiBaseUrl}/expenses/${id}/proofs`, formData); }
|
||||
|
||||
list(filters: { startDate?: string; endDate?: string; categoryId?: string; search?: string; status?: string; tags?: string; duplicatesOnly?: boolean } = {}) {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') params = params.set(key, String(value));
|
||||
});
|
||||
return this.http.get<{ items: Expense[] }>(`${environment.apiBaseUrl}/expenses`, { params });
|
||||
}
|
||||
|
||||
duplicates() {
|
||||
return this.http.get<{ items: DuplicateGroup[] }>(`${environment.apiBaseUrl}/expenses/duplicates`);
|
||||
}
|
||||
|
||||
create(formData: FormData) {
|
||||
return this.http.post<{ item: Expense; warnings?: string[] }>(`${environment.apiBaseUrl}/expenses`, formData);
|
||||
}
|
||||
|
||||
update(id: string, payload: Partial<Expense> & { categoryId: string }) {
|
||||
return this.http.put<{ item: Expense; warnings?: string[] }>(`${environment.apiBaseUrl}/expenses/${id}`, payload);
|
||||
}
|
||||
|
||||
reviewDuplicate(id: string, action: 'CONFIRM' | 'DISMISS' | 'REOPEN') {
|
||||
return this.http.post<{ item: Expense }>(`${environment.apiBaseUrl}/expenses/${id}/duplicate-review`, { action });
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.http.delete<void>(`${environment.apiBaseUrl}/expenses/${id}`);
|
||||
}
|
||||
|
||||
addProof(id: string, formData: FormData) {
|
||||
return this.http.post<{ proofs: Proof[]; expense: Expense }>(`${environment.apiBaseUrl}/expenses/${id}/proofs`, formData);
|
||||
}
|
||||
}
|
||||
|
||||
29
web/src/app/core/services/recurring-expenses.service.ts
Normal file
29
web/src/app/core/services/recurring-expenses.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import type { RecurringExpense } from '../../shared/models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RecurringExpensesService {
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
list() {
|
||||
return this.http.get<{ items: RecurringExpense[] }>(`${environment.apiBaseUrl}/recurring-expenses`);
|
||||
}
|
||||
|
||||
create(payload: Partial<RecurringExpense> & { categoryId: string }) {
|
||||
return this.http.post<{ item: RecurringExpense }>(`${environment.apiBaseUrl}/recurring-expenses`, payload);
|
||||
}
|
||||
|
||||
update(id: string, payload: Partial<RecurringExpense> & { categoryId: string }) {
|
||||
return this.http.put<{ item: RecurringExpense }>(`${environment.apiBaseUrl}/recurring-expenses/${id}`, payload);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.http.delete<void>(`${environment.apiBaseUrl}/recurring-expenses/${id}`);
|
||||
}
|
||||
|
||||
runNow() {
|
||||
return this.http.post<{ message: string }>(`${environment.apiBaseUrl}/recurring-expenses/run`, {});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import type { ReportPreferences, StatsResponse } from '../../shared/models';
|
||||
|
||||
@@ -25,4 +25,12 @@ export class ReportsService {
|
||||
send() {
|
||||
return this.http.post<{ message: string; sentTo: string }>(`${environment.apiBaseUrl}/reports/send`, {});
|
||||
}
|
||||
|
||||
export(filters: { format: 'csv' | 'json' | 'html' | 'pdf'; startDate?: string; endDate?: string; categoryIds?: string; status?: string; tag?: string }) {
|
||||
let params = new HttpParams().set('format', filters.format);
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (key !== 'format' && value) params = params.set(key, value);
|
||||
});
|
||||
return this.http.get(`${environment.apiBaseUrl}/reports/export`, { params, responseType: 'blob' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import type { Expense, ShoppingListExpenseItem, ShoppingListIntegrationSettings, ShoppingListRef, ShoppingListSummary } from '../../shared/models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ShoppingListIntegrationService {
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
getSettings() {
|
||||
return this.http.get<{ item: ShoppingListIntegrationSettings }>(`${environment.apiBaseUrl}/integrations/shopping-list`);
|
||||
}
|
||||
|
||||
updateSettings(payload: { enabled: boolean; baseUrl?: string | null; apiToken?: string; authMode: 'bearer' | 'x-api-token' | 'both'; ownerId?: string | null; defaultListId?: string | null }) {
|
||||
return this.http.put<{ item: ShoppingListIntegrationSettings }>(`${environment.apiBaseUrl}/integrations/shopping-list`, payload);
|
||||
}
|
||||
|
||||
test() {
|
||||
return this.http.post<{ ok: boolean; payload: unknown }>(`${environment.apiBaseUrl}/integrations/shopping-list/test`, {});
|
||||
}
|
||||
|
||||
summary(filters: { start_date?: string; end_date?: string; list_id?: string; owner_id?: string } = {}) {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) params = params.set(key, value);
|
||||
});
|
||||
return this.http.get<ShoppingListSummary>(`${environment.apiBaseUrl}/integrations/shopping-list/summary`, { params });
|
||||
}
|
||||
|
||||
latest(filters: { start_date?: string; end_date?: string; list_id?: string; owner_id?: string; limit?: number } = {}) {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') params = params.set(key, String(value));
|
||||
});
|
||||
return this.http.get<{ items?: ShoppingListExpenseItem[]; data?: ShoppingListExpenseItem[] }>(`${environment.apiBaseUrl}/integrations/shopping-list/latest`, { params });
|
||||
}
|
||||
|
||||
lists(filters: { owner_id?: string; limit?: number } = {}) {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') params = params.set(key, String(value));
|
||||
});
|
||||
return this.http.get<{ items?: ShoppingListRef[]; data?: ShoppingListRef[] }>(`${environment.apiBaseUrl}/integrations/shopping-list/lists`, { params });
|
||||
}
|
||||
|
||||
listExpenses(id: string | number, limit = 50) {
|
||||
const params = new HttpParams().set('limit', String(limit));
|
||||
return this.http.get<{ items?: ShoppingListExpenseItem[]; data?: ShoppingListExpenseItem[] }>(`${environment.apiBaseUrl}/integrations/shopping-list/lists/${id}/expenses`, { params });
|
||||
}
|
||||
|
||||
importList(payload: {
|
||||
listId: string | number;
|
||||
listTitle?: string | null;
|
||||
listCreatedAt?: string | null;
|
||||
categoryId: string;
|
||||
status: 'DRAFT' | 'PENDING';
|
||||
merchant?: string | null;
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
expenseDate?: string | null;
|
||||
tags?: string[];
|
||||
}) {
|
||||
return this.http.post<{ item: Expense; warnings?: string[] }>(`${environment.apiBaseUrl}/integrations/shopping-list/import-list`, payload);
|
||||
}
|
||||
|
||||
importItem(payload: {
|
||||
expenseId?: string | number | null;
|
||||
listId?: string | number | null;
|
||||
listTitle?: string | null;
|
||||
categoryId: string;
|
||||
status: 'DRAFT' | 'PENDING';
|
||||
title: string;
|
||||
amount: number;
|
||||
expenseDate: string;
|
||||
merchant?: string | null;
|
||||
ownerName?: string | null;
|
||||
description?: string | null;
|
||||
tags?: string[];
|
||||
}) {
|
||||
return this.http.post<{ item: Expense; warnings?: string[] }>(`${environment.apiBaseUrl}/integrations/shopping-list/import-item`, payload);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import type { StatsResponse } from '../../shared/models';
|
||||
import type { CashflowResponse, StatsResponse } from '../../shared/models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class StatsService {
|
||||
private readonly http = inject(HttpClient);
|
||||
overview(filters: { startDate?: string; endDate?: string; categoryIds?: string; bucket?: 'month' | 'quarter' | 'year' }) { let params = new HttpParams(); Object.entries(filters).forEach(([key, value]) => { if (value) params = params.set(key, value); }); return this.http.get<StatsResponse>(`${environment.apiBaseUrl}/statistics/overview`, { params }); }
|
||||
|
||||
overview(filters: { startDate?: string; endDate?: string; categoryIds?: string; bucket?: 'month' | 'quarter' | 'year'; tag?: string; status?: string }) {
|
||||
let params = new HttpParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value) params = params.set(key, value);
|
||||
});
|
||||
return this.http.get<StatsResponse>(`${environment.apiBaseUrl}/statistics/overview`, { params });
|
||||
}
|
||||
|
||||
cashflow() {
|
||||
return this.http.get<CashflowResponse>(`${environment.apiBaseUrl}/statistics/cashflow`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ const translations: Record<UiLanguage, Record<string, string>> = {
|
||||
'action.unblock': 'Odblokuj',
|
||||
'action.setUser': 'Ustaw USER',
|
||||
'action.setAdmin': 'Ustaw ADMIN',
|
||||
'action.import': 'Importuj',
|
||||
|
||||
'theme.label': 'Motyw',
|
||||
'theme.dark': 'Ciemny',
|
||||
@@ -227,6 +228,87 @@ const translations: Record<UiLanguage, Record<string, string>> = {
|
||||
'admin.statusUpdated': 'Status konta został zaktualizowany.',
|
||||
'admin.statusError': 'Nie udało się zmienić statusu.',
|
||||
|
||||
|
||||
'nav.cashflow': 'Cashflow',
|
||||
'nav.budgets': 'Budżety',
|
||||
'nav.recurring': 'Cykliczne',
|
||||
|
||||
'action.saveDraft': 'Zapisz szkic',
|
||||
|
||||
'status.draft': 'Szkic',
|
||||
'status.pending': 'Oczekuje',
|
||||
'status.approved': 'Zatwierdzony',
|
||||
'status.rejected': 'Odrzucony',
|
||||
|
||||
'dashboard.cashflowHint': 'Przegląd kosztów, budżetów, duplikatów i przyszłych obciążeń.',
|
||||
'dashboard.budgetUsage': 'Wykorzystanie budżetu',
|
||||
|
||||
'expenses.field.status': 'Status',
|
||||
'expenses.field.tags': 'Tagi',
|
||||
'expenses.field.customFields': 'Własne pola',
|
||||
'expenses.field.customKey': 'Nazwa pola',
|
||||
'expenses.field.customValue': 'Wartość',
|
||||
'expenses.tagPlaceholder': 'np. projekt-x, marketing',
|
||||
'expenses.noCustomFields': 'Brak własnych pól.',
|
||||
'expenses.attachmentsSelected': 'Wybrane załączniki',
|
||||
'expenses.duplicatesTitle': 'Wykryte potencjalne duplikaty',
|
||||
'expenses.potentialMatches': 'podobnych pozycji',
|
||||
'expenses.duplicatesOnly': 'Pokaż tylko potencjalne duplikaty',
|
||||
'expenses.duplicate': 'Duplikat',
|
||||
'expenses.draftSaved': 'Szkic wydatku został zapisany.',
|
||||
|
||||
'stats.tags': 'Analiza tagów',
|
||||
|
||||
'reports.exportTitle': 'Eksport raportów',
|
||||
'reports.exportError': 'Nie udało się wyeksportować raportu.',
|
||||
|
||||
'budget.title': 'Budżety miesięczne',
|
||||
'budget.subtitle': 'Limity miesięczne ogólne i per kategoria z alertami zużycia.',
|
||||
'budget.new': 'Nowy budżet',
|
||||
'budget.edit': 'Edytuj budżet',
|
||||
'budget.month': 'Miesiąc',
|
||||
'budget.name': 'Nazwa',
|
||||
'budget.amount': 'Kwota budżetu',
|
||||
'budget.category': 'Kategoria',
|
||||
'budget.overall': 'Budżet ogólny',
|
||||
'budget.thresholds': 'Progi alertów',
|
||||
'budget.total': 'Łączny budżet',
|
||||
'budget.spent': 'Wydano',
|
||||
'budget.usage': 'Zużycie',
|
||||
'budget.alerts': 'Alerty budżetowe',
|
||||
'budget.saved': 'Budżet został zapisany.',
|
||||
'budget.saveError': 'Nie udało się zapisać budżetu.',
|
||||
'budget.deleted': 'Budżet został usunięty.',
|
||||
'budget.deleteError': 'Nie udało się usunąć budżetu.',
|
||||
|
||||
'recurring.title': 'Cykliczne wydatki',
|
||||
'recurring.subtitle': 'Szablony kosztów generowanych automatycznie w czasie.',
|
||||
'recurring.new': 'Nowy harmonogram',
|
||||
'recurring.edit': 'Edytuj harmonogram',
|
||||
'recurring.frequency': 'Częstotliwość',
|
||||
'recurring.weekly': 'Co tydzień',
|
||||
'recurring.monthly': 'Co miesiąc',
|
||||
'recurring.yearly': 'Co rok',
|
||||
'recurring.interval': 'Interwał',
|
||||
'recurring.startDate': 'Data startu',
|
||||
'recurring.nextRunDate': 'Następne utworzenie',
|
||||
'recurring.runNow': 'Uruchom teraz',
|
||||
'recurring.saved': 'Harmonogram został zapisany.',
|
||||
'recurring.saveError': 'Nie udało się zapisać harmonogramu.',
|
||||
'recurring.deleted': 'Harmonogram został usunięty.',
|
||||
'recurring.deleteError': 'Nie udało się usunąć harmonogramu.',
|
||||
'recurring.ran': 'Cykliczne wydatki zostały przetworzone.',
|
||||
'recurring.badge': 'Cykliczny',
|
||||
|
||||
'cashflow.subtitle': 'Rzeczywiste koszty, budżet, prognoza i najbliższe cykliczne obciążenia.',
|
||||
'cashflow.actual': 'Rzeczywiste koszty',
|
||||
'cashflow.budget': 'Budżet',
|
||||
'cashflow.forecast': 'Prognoza miesiąca',
|
||||
'cashflow.pending': 'Do akceptacji',
|
||||
'cashflow.duplicates': 'Duplikaty',
|
||||
'cashflow.trend': 'Trend cashflow',
|
||||
'cashflow.statusSummary': 'Statusy wydatków',
|
||||
'cashflow.upcomingRecurring': 'Nadchodzące cykliczne',
|
||||
'common.none': 'Brak',
|
||||
'common.select': 'Wybierz',
|
||||
'common.noData': 'Brak danych.',
|
||||
@@ -237,12 +319,67 @@ const translations: Record<UiLanguage, Record<string, string>> = {
|
||||
'common.blocked': 'Zablokowany',
|
||||
'common.selected': 'OK',
|
||||
|
||||
'nav.integrations': 'Integracje',
|
||||
|
||||
'action.testConnection': 'Test połączenia',
|
||||
'action.refresh': 'Odśwież',
|
||||
|
||||
'expenses.duplicateDismissed': 'Duplikat został odrzucony.',
|
||||
'expenses.duplicateConfirmed': 'Wydatek został oznaczony jako potwierdzony duplikat.',
|
||||
'expenses.duplicateReopened': 'Sprawdzenie duplikatu zostało przywrócone.',
|
||||
'expenses.duplicateStatus.open': 'Do sprawdzenia',
|
||||
'expenses.duplicateStatus.confirmed': 'Potwierdzony',
|
||||
'expenses.duplicateStatus.dismissed': 'Odrzucony',
|
||||
|
||||
'recurring.endDate': 'Data końcowa',
|
||||
'recurring.maxOccurrences': 'Maks. liczba utworzeń',
|
||||
'recurring.generatedCount': 'Utworzono',
|
||||
|
||||
'integrations.title': 'Integracje',
|
||||
'integrations.subtitle': 'Połączenia per użytkownik z zewnętrznymi źródłami danych oraz import historyczny.',
|
||||
'integrations.shoppingList': 'Lista zakupów API',
|
||||
'integrations.enabled': 'Włącz integrację dla tego użytkownika',
|
||||
'integrations.baseUrl': 'URL API',
|
||||
'integrations.apiToken': 'Token API',
|
||||
'integrations.keepToken': 'Zostaw puste, aby zachować obecny token.',
|
||||
'integrations.authMode': 'Tryb autoryzacji',
|
||||
'integrations.ownerId': 'Domyślny owner ID',
|
||||
'integrations.defaultListId': 'Domyślne list ID',
|
||||
'integrations.history': 'Import historyczny',
|
||||
'integrations.period': 'Miesiąc / rok',
|
||||
'integrations.limit': 'Limit rekordów',
|
||||
'integrations.summary': 'Podsumowanie zewnętrzne',
|
||||
'integrations.latest': 'Wydatki z wybranego okresu',
|
||||
'integrations.lists': 'Listy zakupowe z okresu',
|
||||
'integrations.listExpenses': 'Pozycje wybranej listy',
|
||||
'integrations.importTitle': 'Import do lokalnych wydatków',
|
||||
'integrations.importSelectedList': 'Importuj wybraną listę jako 1 wydatek',
|
||||
'integrations.selectListHint': 'Wybierz listę po lewej, aby podejrzeć pozycje i zaimportować całą listę lub pojedyncze wydatki.',
|
||||
'integrations.selectedListSummary': 'Pozycje / suma',
|
||||
'integrations.tags': 'Tagi importu',
|
||||
'integrations.tagsHint': 'Oddzielaj tagi przecinkami.',
|
||||
'integrations.externalSpend': 'Suma zewnętrzna',
|
||||
'integrations.externalCount': 'Rekordy zewnętrzne',
|
||||
'integrations.notConfigured': 'Skonfiguruj integrację i zapisz ustawienia, aby pobrać dane.',
|
||||
'integrations.saveSuccess': 'Ustawienia integracji zostały zapisane.',
|
||||
'integrations.saveError': 'Nie udało się zapisać ustawień integracji.',
|
||||
'integrations.testSuccess': 'Połączenie z zewnętrznym API działa.',
|
||||
'integrations.testError': 'Nie udało się połączyć z zewnętrznym API.',
|
||||
'integrations.loadError': 'Nie udało się pobrać danych integracji.',
|
||||
'integrations.importListSuccess': 'Lista zakupowa została zaimportowana jako lokalny wydatek.',
|
||||
'integrations.importItemSuccess': 'Pozycja z listy zakupowej została zaimportowana.',
|
||||
'integrations.importError': 'Nie udało się zaimportować danych z list zakupowych.',
|
||||
|
||||
'dashboard.externalSpend': 'Zewnętrzna suma',
|
||||
'dashboard.externalRecords': 'Zewnętrzne rekordy',
|
||||
|
||||
'table.title': 'Tytuł',
|
||||
'table.merchant': 'Kontrahent',
|
||||
'table.date': 'Data',
|
||||
'table.amount': 'Kwota',
|
||||
'table.count': 'Liczba',
|
||||
'table.category': 'Kategoria',
|
||||
'table.actions': 'Akcje',
|
||||
|
||||
'toast.ready': 'Gotowe',
|
||||
'toast.error': 'Błąd',
|
||||
@@ -288,6 +425,7 @@ const translations: Record<UiLanguage, Record<string, string>> = {
|
||||
'action.unblock': 'Unblock',
|
||||
'action.setUser': 'Set USER',
|
||||
'action.setAdmin': 'Set ADMIN',
|
||||
'action.import': 'Import',
|
||||
|
||||
'theme.label': 'Theme',
|
||||
'theme.dark': 'Dark',
|
||||
@@ -471,6 +609,87 @@ const translations: Record<UiLanguage, Record<string, string>> = {
|
||||
'admin.statusUpdated': 'Account status updated successfully.',
|
||||
'admin.statusError': 'Failed to change the account status.',
|
||||
|
||||
|
||||
'nav.cashflow': 'Cashflow',
|
||||
'nav.budgets': 'Budgets',
|
||||
'nav.recurring': 'Recurring',
|
||||
|
||||
'action.saveDraft': 'Save draft',
|
||||
|
||||
'status.draft': 'Draft',
|
||||
'status.pending': 'Pending',
|
||||
'status.approved': 'Approved',
|
||||
'status.rejected': 'Rejected',
|
||||
|
||||
'dashboard.cashflowHint': 'Overview of spend, budgets, duplicates, and upcoming recurring charges.',
|
||||
'dashboard.budgetUsage': 'Budget usage',
|
||||
|
||||
'expenses.field.status': 'Status',
|
||||
'expenses.field.tags': 'Tags',
|
||||
'expenses.field.customFields': 'Custom fields',
|
||||
'expenses.field.customKey': 'Field name',
|
||||
'expenses.field.customValue': 'Value',
|
||||
'expenses.tagPlaceholder': 'e.g. project-x, marketing',
|
||||
'expenses.noCustomFields': 'No custom fields.',
|
||||
'expenses.attachmentsSelected': 'Selected attachments',
|
||||
'expenses.duplicatesTitle': 'Potential duplicates detected',
|
||||
'expenses.potentialMatches': 'similar entries',
|
||||
'expenses.duplicatesOnly': 'Show only potential duplicates',
|
||||
'expenses.duplicate': 'Duplicate',
|
||||
'expenses.draftSaved': 'Expense draft was saved.',
|
||||
|
||||
'stats.tags': 'Tag analysis',
|
||||
|
||||
'reports.exportTitle': 'Report export',
|
||||
'reports.exportError': 'Failed to export the report.',
|
||||
|
||||
'budget.title': 'Monthly budgets',
|
||||
'budget.subtitle': 'Monthly limits overall and per category with usage alerts.',
|
||||
'budget.new': 'New budget',
|
||||
'budget.edit': 'Edit budget',
|
||||
'budget.month': 'Month',
|
||||
'budget.name': 'Name',
|
||||
'budget.amount': 'Budget amount',
|
||||
'budget.category': 'Category',
|
||||
'budget.overall': 'Overall budget',
|
||||
'budget.thresholds': 'Alert thresholds',
|
||||
'budget.total': 'Total budget',
|
||||
'budget.spent': 'Spent',
|
||||
'budget.usage': 'Usage',
|
||||
'budget.alerts': 'Budget alerts',
|
||||
'budget.saved': 'Budget was saved.',
|
||||
'budget.saveError': 'Failed to save budget.',
|
||||
'budget.deleted': 'Budget was deleted.',
|
||||
'budget.deleteError': 'Failed to delete budget.',
|
||||
|
||||
'recurring.title': 'Recurring expenses',
|
||||
'recurring.subtitle': 'Templates for costs generated automatically over time.',
|
||||
'recurring.new': 'New schedule',
|
||||
'recurring.edit': 'Edit schedule',
|
||||
'recurring.frequency': 'Frequency',
|
||||
'recurring.weekly': 'Weekly',
|
||||
'recurring.monthly': 'Monthly',
|
||||
'recurring.yearly': 'Yearly',
|
||||
'recurring.interval': 'Interval',
|
||||
'recurring.startDate': 'Start date',
|
||||
'recurring.nextRunDate': 'Next run date',
|
||||
'recurring.runNow': 'Run now',
|
||||
'recurring.saved': 'Recurring schedule was saved.',
|
||||
'recurring.saveError': 'Failed to save recurring schedule.',
|
||||
'recurring.deleted': 'Recurring schedule was deleted.',
|
||||
'recurring.deleteError': 'Failed to delete recurring schedule.',
|
||||
'recurring.ran': 'Recurring expenses were processed.',
|
||||
'recurring.badge': 'Recurring',
|
||||
|
||||
'cashflow.subtitle': 'Actual spend, budget, forecast, and upcoming recurring charges.',
|
||||
'cashflow.actual': 'Actual spend',
|
||||
'cashflow.budget': 'Budget',
|
||||
'cashflow.forecast': 'Month forecast',
|
||||
'cashflow.pending': 'Pending approval',
|
||||
'cashflow.duplicates': 'Duplicates',
|
||||
'cashflow.trend': 'Cashflow trend',
|
||||
'cashflow.statusSummary': 'Expense statuses',
|
||||
'cashflow.upcomingRecurring': 'Upcoming recurring',
|
||||
'common.none': 'None',
|
||||
'common.select': 'Select',
|
||||
'common.noData': 'No data.',
|
||||
@@ -481,12 +700,67 @@ const translations: Record<UiLanguage, Record<string, string>> = {
|
||||
'common.blocked': 'Blocked',
|
||||
'common.selected': 'OK',
|
||||
|
||||
'nav.integrations': 'Integrations',
|
||||
|
||||
'action.testConnection': 'Test connection',
|
||||
'action.refresh': 'Refresh',
|
||||
|
||||
'expenses.duplicateDismissed': 'Duplicate flag was dismissed.',
|
||||
'expenses.duplicateConfirmed': 'Expense was marked as a confirmed duplicate.',
|
||||
'expenses.duplicateReopened': 'Duplicate review was reopened.',
|
||||
'expenses.duplicateStatus.open': 'Needs review',
|
||||
'expenses.duplicateStatus.confirmed': 'Confirmed',
|
||||
'expenses.duplicateStatus.dismissed': 'Dismissed',
|
||||
|
||||
'recurring.endDate': 'End date',
|
||||
'recurring.maxOccurrences': 'Max occurrences',
|
||||
'recurring.generatedCount': 'Generated',
|
||||
|
||||
'integrations.title': 'Integrations',
|
||||
'integrations.subtitle': 'Per-user connections to external data sources with historical backfill.',
|
||||
'integrations.shoppingList': 'Shopping list API',
|
||||
'integrations.enabled': 'Enable integration for this user',
|
||||
'integrations.baseUrl': 'API URL',
|
||||
'integrations.apiToken': 'API token',
|
||||
'integrations.keepToken': 'Leave blank to keep the current token.',
|
||||
'integrations.authMode': 'Authorization mode',
|
||||
'integrations.ownerId': 'Default owner ID',
|
||||
'integrations.defaultListId': 'Default list ID',
|
||||
'integrations.history': 'Historical import',
|
||||
'integrations.period': 'Month / year',
|
||||
'integrations.limit': 'Record limit',
|
||||
'integrations.summary': 'External summary',
|
||||
'integrations.latest': 'Expenses for selected period',
|
||||
'integrations.lists': 'Shopping lists for period',
|
||||
'integrations.listExpenses': 'Entries for selected list',
|
||||
'integrations.importTitle': 'Import into local expenses',
|
||||
'integrations.importSelectedList': 'Import selected list as 1 expense',
|
||||
'integrations.selectListHint': 'Select a list on the left to preview entries and import the whole list or individual expenses.',
|
||||
'integrations.selectedListSummary': 'Entries / total',
|
||||
'integrations.tags': 'Import tags',
|
||||
'integrations.tagsHint': 'Separate tags with commas.',
|
||||
'integrations.externalSpend': 'External spend',
|
||||
'integrations.externalCount': 'External records',
|
||||
'integrations.notConfigured': 'Configure the integration and save settings to load data.',
|
||||
'integrations.saveSuccess': 'Integration settings were saved.',
|
||||
'integrations.saveError': 'Failed to save integration settings.',
|
||||
'integrations.testSuccess': 'Connection to the external API works.',
|
||||
'integrations.testError': 'Failed to connect to the external API.',
|
||||
'integrations.loadError': 'Failed to load integration data.',
|
||||
'integrations.importListSuccess': 'The shopping list was imported as a local expense.',
|
||||
'integrations.importItemSuccess': 'The shopping list entry was imported.',
|
||||
'integrations.importError': 'Failed to import data from the shopping list API.',
|
||||
|
||||
'dashboard.externalSpend': 'External spend',
|
||||
'dashboard.externalRecords': 'External records',
|
||||
|
||||
'table.title': 'Title',
|
||||
'table.merchant': 'Merchant',
|
||||
'table.date': 'Date',
|
||||
'table.amount': 'Amount',
|
||||
'table.count': 'Count',
|
||||
'table.category': 'Category',
|
||||
'table.actions': 'Actions',
|
||||
|
||||
'toast.ready': 'Done',
|
||||
'toast.error': 'Error',
|
||||
|
||||
Reference in New Issue
Block a user