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/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ import {
import { apiClient } from '../lib/api';
import { formatCurrency, formatDate } from '../lib/utils';
import LoadingSpinner from './LoadingSpinner';
import type { Account, Transaction } from '../types/api';
export default function TransactionsList() {
const [searchTerm, setSearchTerm] = useState('');
@@ -23,7 +24,7 @@ export default function TransactionsList() {
const {
data: accounts
} = useQuery({
} = useQuery<Account[]>({
queryKey: ['accounts'],
queryFn: apiClient.getAccounts,
});
@@ -33,18 +34,18 @@ export default function TransactionsList() {
isLoading: transactionsLoading,
error: transactionsError,
refetch: refetchTransactions
} = useQuery({
} = useQuery<Transaction[]>({
queryKey: ['transactions', selectedAccount, startDate, endDate],
queryFn: () => apiClient.getTransactions({
account_id: selectedAccount || undefined,
start_date: startDate || undefined,
end_date: endDate || undefined,
accountId: selectedAccount || undefined,
startDate: startDate || undefined,
endDate: endDate || undefined,
}),
});
const filteredTransactions = (transactions || []).filter(transaction => {
// 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);
return false;
}
@@ -239,7 +240,7 @@ export default function TransactionsList() {
const isPositive = transaction.amount > 0;
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-1">
<div className="flex items-start space-x-3">
@@ -274,8 +275,8 @@ export default function TransactionsList() {
<p>Ref: {transaction.reference}</p>
)}
{transaction.internal_id && (
<p>ID: {transaction.internal_id}</p>
{transaction.internal_transaction_id && (
<p>ID: {transaction.internal_transaction_id}</p>
)}
</div>
</div>
@@ -289,9 +290,9 @@ export default function TransactionsList() {
{isPositive ? '+' : ''}{formatCurrency(transaction.amount, transaction.currency)}
</p>
<p className="text-sm text-gray-500">
{transaction.transaction_date ? formatDate(transaction.transaction_date) : 'No date'}
{transaction.date ? formatDate(transaction.date) : 'No date'}
</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">
Booked: {formatDate(transaction.booking_date)}
</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 {
id: string;
internal_id?: string;
internal_transaction_id: string | null;
account_id: string;
amount: number;
currency: string;
description: string;
transaction_date: string;
date: string;
status: string;
// Optional fields that may be present in some transactions
booking_date?: string;
value_date?: string;
creditor_name?: string;
debtor_name?: string;
reference?: string;
category?: string;
created_at: string;
updated_at: string;
created_at?: string;
updated_at?: string;
}
// Type for raw transaction data from API (before sanitization)
@@ -68,7 +69,7 @@ export interface Bank {
export interface ApiResponse<T> {
data: T;
message?: string;
status: string;
success: boolean;
}
export interface PaginatedResponse<T> {