Fix api in lib folder.

This commit is contained in:
Elisiário Couto
2025-09-08 19:08:30 +01:00
committed by Elisiário Couto
parent 26487cff89
commit abacfd78c8
7 changed files with 137 additions and 21 deletions

1
.gitignore vendored
View File

@@ -14,7 +14,6 @@ dist/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/
lib64/ lib64/
parts/ parts/
sdist/ sdist/

View File

@@ -10,6 +10,7 @@ import {
import { apiClient } from '../lib/api'; import { apiClient } from '../lib/api';
import { formatCurrency, formatDate } from '../lib/utils'; import { formatCurrency, formatDate } from '../lib/utils';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
import type { Account, Balance } from '../types/api';
export default function AccountsOverview() { export default function AccountsOverview() {
const { const {
@@ -17,14 +18,14 @@ export default function AccountsOverview() {
isLoading: accountsLoading, isLoading: accountsLoading,
error: accountsError, error: accountsError,
refetch: refetchAccounts refetch: refetchAccounts
} = useQuery({ } = useQuery<Account[]>({
queryKey: ['accounts'], queryKey: ['accounts'],
queryFn: apiClient.getAccounts, queryFn: apiClient.getAccounts,
}); });
const { const {
data: balances data: balances
} = useQuery({ } = useQuery<Balance[]>({
queryKey: ['balances'], queryKey: ['balances'],
queryFn: () => apiClient.getBalances(), queryFn: () => apiClient.getBalances(),
}); });

View File

@@ -15,6 +15,7 @@ import AccountsOverview from './AccountsOverview';
import TransactionsList from './TransactionsList'; import TransactionsList from './TransactionsList';
import ErrorBoundary from './ErrorBoundary'; import ErrorBoundary from './ErrorBoundary';
import { cn } from '../lib/utils'; import { cn } from '../lib/utils';
import type { Account } from '../types/api';
type TabType = 'overview' | 'transactions' | 'analytics'; type TabType = 'overview' | 'transactions' | 'analytics';
@@ -22,7 +23,7 @@ export default function Dashboard() {
const [activeTab, setActiveTab] = useState<TabType>('overview'); const [activeTab, setActiveTab] = useState<TabType>('overview');
const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(false);
const { data: accounts } = useQuery({ const { data: accounts } = useQuery<Account[]>({
queryKey: ['accounts'], queryKey: ['accounts'],
queryFn: apiClient.getAccounts, queryFn: apiClient.getAccounts,
}); });

View File

@@ -13,6 +13,7 @@ import {
import { apiClient } from '../lib/api'; import { apiClient } from '../lib/api';
import { formatCurrency, formatDate } from '../lib/utils'; import { formatCurrency, formatDate } from '../lib/utils';
import LoadingSpinner from './LoadingSpinner'; import LoadingSpinner from './LoadingSpinner';
import type { Account, Transaction } from '../types/api';
export default function TransactionsList() { export default function TransactionsList() {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
@@ -23,7 +24,7 @@ export default function TransactionsList() {
const { const {
data: accounts data: accounts
} = useQuery({ } = useQuery<Account[]>({
queryKey: ['accounts'], queryKey: ['accounts'],
queryFn: apiClient.getAccounts, queryFn: apiClient.getAccounts,
}); });
@@ -33,18 +34,18 @@ export default function TransactionsList() {
isLoading: transactionsLoading, isLoading: transactionsLoading,
error: transactionsError, error: transactionsError,
refetch: refetchTransactions refetch: refetchTransactions
} = useQuery({ } = useQuery<Transaction[]>({
queryKey: ['transactions', selectedAccount, startDate, endDate], queryKey: ['transactions', selectedAccount, startDate, endDate],
queryFn: () => apiClient.getTransactions({ queryFn: () => apiClient.getTransactions({
account_id: selectedAccount || undefined, accountId: selectedAccount || undefined,
start_date: startDate || undefined, startDate: startDate || undefined,
end_date: endDate || undefined, endDate: endDate || undefined,
}), }),
}); });
const filteredTransactions = (transactions || []).filter(transaction => { const filteredTransactions = (transactions || []).filter(transaction => {
// Additional validation (API client should have already filtered out invalid ones) // Additional validation (API client should have already filtered out invalid ones)
if (!transaction || !transaction.id) { if (!transaction || !transaction.account_id) {
console.warn('Invalid transaction found after API filtering:', transaction); console.warn('Invalid transaction found after API filtering:', transaction);
return false; return false;
} }
@@ -239,7 +240,7 @@ export default function TransactionsList() {
const isPositive = transaction.amount > 0; const isPositive = transaction.amount > 0;
return ( return (
<div key={transaction.id} className="p-6 hover:bg-gray-50 transition-colors"> <div key={transaction.internal_transaction_id || `${transaction.account_id}-${transaction.date}-${transaction.amount}`} className="p-6 hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-start space-x-3"> <div className="flex items-start space-x-3">
@@ -274,8 +275,8 @@ export default function TransactionsList() {
<p>Ref: {transaction.reference}</p> <p>Ref: {transaction.reference}</p>
)} )}
{transaction.internal_id && ( {transaction.internal_transaction_id && (
<p>ID: {transaction.internal_id}</p> <p>ID: {transaction.internal_transaction_id}</p>
)} )}
</div> </div>
</div> </div>
@@ -289,9 +290,9 @@ export default function TransactionsList() {
{isPositive ? '+' : ''}{formatCurrency(transaction.amount, transaction.currency)} {isPositive ? '+' : ''}{formatCurrency(transaction.amount, transaction.currency)}
</p> </p>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
{transaction.transaction_date ? formatDate(transaction.transaction_date) : 'No date'} {transaction.date ? formatDate(transaction.date) : 'No date'}
</p> </p>
{transaction.booking_date && transaction.booking_date !== transaction.transaction_date && ( {transaction.booking_date && transaction.booking_date !== transaction.date && (
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400">
Booked: {formatDate(transaction.booking_date)} Booked: {formatDate(transaction.booking_date)}
</p> </p>

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

@@ -0,0 +1,67 @@
import axios from 'axios';
import type { Account, Transaction, Balance, ApiResponse } from '../types/api';
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
export const apiClient = {
// Get all accounts
getAccounts: async (): Promise<Account[]> => {
const response = await api.get<ApiResponse<Account[]>>('/accounts');
return response.data.data;
},
// Get account by ID
getAccount: async (id: string): Promise<Account> => {
const response = await api.get<ApiResponse<Account>>(`/accounts/${id}`);
return response.data.data;
},
// Get all balances
getBalances: async (): Promise<Balance[]> => {
const response = await api.get<ApiResponse<Balance[]>>('/balances');
return response.data.data;
},
// Get balances for specific account
getAccountBalances: async (accountId: string): Promise<Balance[]> => {
const response = await api.get<ApiResponse<Balance[]>>(`/accounts/${accountId}/balances`);
return response.data.data;
},
// Get transactions with optional filters
getTransactions: async (params?: {
accountId?: string;
startDate?: string;
endDate?: string;
page?: number;
perPage?: number;
search?: string;
}): Promise<Transaction[]> => {
const queryParams = new URLSearchParams();
if (params?.accountId) queryParams.append('account_id', params.accountId);
if (params?.startDate) queryParams.append('start_date', params.startDate);
if (params?.endDate) queryParams.append('end_date', params.endDate);
if (params?.page) queryParams.append('page', params.page.toString());
if (params?.perPage) queryParams.append('per_page', params.perPage.toString());
if (params?.search) queryParams.append('search', params.search);
const response = await api.get<ApiResponse<Transaction[]>>(`/transactions?${queryParams.toString()}`);
return response.data.data;
},
// Get transaction by ID
getTransaction: async (id: string): Promise<Transaction> => {
const response = await api.get<ApiResponse<Transaction>>(`/transactions/${id}`);
return response.data.data;
},
};
export default apiClient;

46
frontend/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,46 @@
import { clsx, type ClassValue } from 'clsx';
export function cn(...inputs: ClassValue[]) {
return clsx(inputs);
}
export function formatCurrency(amount: number, currency: string = 'EUR'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount);
}
export function formatDate(date: string): string {
if (!date) return 'No date';
const parsedDate = new Date(date);
if (isNaN(parsedDate.getTime())) {
console.warn('Invalid date string:', date);
return 'Invalid date';
}
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
}).format(parsedDate);
}
export function formatDateTime(date: string): string {
if (!date) return 'No date';
const parsedDate = new Date(date);
if (isNaN(parsedDate.getTime())) {
console.warn('Invalid date string:', date);
return 'Invalid date';
}
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}).format(parsedDate);
}

View File

@@ -11,21 +11,22 @@ export interface Account {
} }
export interface Transaction { export interface Transaction {
id: string; internal_transaction_id: string | null;
internal_id?: string;
account_id: string; account_id: string;
amount: number; amount: number;
currency: string; currency: string;
description: string; description: string;
transaction_date: string; date: string;
status: string;
// Optional fields that may be present in some transactions
booking_date?: string; booking_date?: string;
value_date?: string; value_date?: string;
creditor_name?: string; creditor_name?: string;
debtor_name?: string; debtor_name?: string;
reference?: string; reference?: string;
category?: string; category?: string;
created_at: string; created_at?: string;
updated_at: string; updated_at?: string;
} }
// Type for raw transaction data from API (before sanitization) // Type for raw transaction data from API (before sanitization)
@@ -68,7 +69,7 @@ export interface Bank {
export interface ApiResponse<T> { export interface ApiResponse<T> {
data: T; data: T;
message?: string; message?: string;
status: string; success: boolean;
} }
export interface PaginatedResponse<T> { export interface PaginatedResponse<T> {