diff --git a/frontend/src/components/RawTransactionModal.tsx b/frontend/src/components/RawTransactionModal.tsx new file mode 100644 index 0000000..d97e7af --- /dev/null +++ b/frontend/src/components/RawTransactionModal.tsx @@ -0,0 +1,116 @@ +import { X, Copy, Check } from "lucide-react"; +import { useState } from "react"; +import type { RawTransactionData } from "../types/api"; + +interface RawTransactionModalProps { + isOpen: boolean; + onClose: () => void; + rawTransaction: RawTransactionData | undefined; + transactionId: string; +} + +export default function RawTransactionModal({ + isOpen, + onClose, + rawTransaction, + transactionId, +}: RawTransactionModalProps) { + const [copied, setCopied] = useState(false); + + if (!isOpen) return null; + + const handleCopy = async () => { + if (!rawTransaction) return; + + try { + await navigator.clipboard.writeText( + JSON.stringify(rawTransaction, null, 2) + ); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy to clipboard:", err); + } + }; + + return ( +
+
+ {/* Background overlay */} +
+ + {/* Modal panel */} +
+
+
+

+ Raw Transaction Data +

+
+ + +
+
+ +
+

+ Transaction ID: {transactionId} +

+
+ + {rawTransaction ? ( +
+
+                  {JSON.stringify(rawTransaction, null, 2)}
+                
+
+ ) : ( +
+

+ Raw transaction data is not available for this transaction. +

+

+ Try refreshing the page or check if the transaction was fetched with summary_only=false. +

+
+ )} +
+ +
+ +
+
+
+
+ ); +} diff --git a/frontend/src/components/TransactionsList.tsx b/frontend/src/components/TransactionsList.tsx index 23c64d0..a210116 100644 --- a/frontend/src/components/TransactionsList.tsx +++ b/frontend/src/components/TransactionsList.tsx @@ -9,10 +9,12 @@ import { RefreshCw, AlertCircle, X, + Eye, } from "lucide-react"; import { apiClient } from "../lib/api"; import { formatCurrency, formatDate } from "../lib/utils"; import LoadingSpinner from "./LoadingSpinner"; +import RawTransactionModal from "./RawTransactionModal"; import type { Account, Transaction } from "../types/api"; export default function TransactionsList() { @@ -21,6 +23,8 @@ export default function TransactionsList() { const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); const [showFilters, setShowFilters] = useState(false); + const [showRawModal, setShowRawModal] = useState(false); + const [selectedTransaction, setSelectedTransaction] = useState(null); const { data: accounts } = useQuery({ queryKey: ["accounts"], @@ -39,6 +43,7 @@ export default function TransactionsList() { accountId: selectedAccount || undefined, startDate: startDate || undefined, endDate: endDate || undefined, + summaryOnly: false, // Always fetch raw transaction data }), }); @@ -74,6 +79,16 @@ export default function TransactionsList() { setEndDate(""); }; + const handleViewRaw = (transaction: Transaction) => { + setSelectedTransaction(transaction); + setShowRawModal(true); + }; + + const handleCloseModal = () => { + setShowRawModal(false); + setSelectedTransaction(null); + }; + const hasActiveFilters = searchTerm || selectedAccount || startDate || endDate; @@ -248,13 +263,13 @@ export default function TransactionsList() { const account = accounts?.find( (acc) => acc.id === transaction.account_id, ); - const isPositive = transaction.amount > 0; + const isPositive = transaction.transaction_value > 0; return (
@@ -307,33 +322,51 @@ export default function TransactionsList() {
-
-

- {isPositive ? "+" : ""} - {formatCurrency(transaction.amount, transaction.currency)} -

-

- {transaction.date - ? formatDate(transaction.date) - : "No date"} -

- {transaction.booking_date && - transaction.booking_date !== transaction.date && ( -

- Booked: {formatDate(transaction.booking_date)} -

- )} -
+
+
+ +
+

+ {isPositive ? "+" : ""} + {formatCurrency(transaction.transaction_value, transaction.transaction_currency)} +

+

+ {transaction.transaction_date + ? formatDate(transaction.transaction_date) + : "No date"} +

+ {transaction.booking_date && + transaction.booking_date !== transaction.transaction_date && ( +

+ Booked: {formatDate(transaction.booking_date)} +

+ )} +
); })} )} + + {/* Raw Transaction Modal */} + ); } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index c03ef8a..1818cb0 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -56,6 +56,7 @@ export const apiClient = { page?: number; perPage?: number; search?: string; + summaryOnly?: boolean; }): Promise => { const queryParams = new URLSearchParams(); @@ -66,6 +67,9 @@ export const apiClient = { if (params?.perPage) queryParams.append("per_page", params.perPage.toString()); if (params?.search) queryParams.append("search", params.search); + if (params?.summaryOnly !== undefined) { + queryParams.append("summary_only", params.summaryOnly.toString()); + } const response = await api.get>( `/transactions?${queryParams.toString()}`, diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 1111845..de7db2c 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -17,15 +17,55 @@ export interface Account { balances: AccountBalance[]; } +export interface RawTransactionData { + transactionId?: string; + bookingDate?: string; + valueDate?: string; + bookingDateTime?: string; + valueDateTime?: string; + transactionAmount?: { + amount: string; + currency: string; + }; + currencyExchange?: { + instructedAmount?: { + amount: string; + currency: string; + }; + sourceCurrency?: string; + exchangeRate?: string; + unitCurrency?: string; + targetCurrency?: string; + }; + creditorName?: string; + debtorName?: string; + debtorAccount?: { + iban?: string; + }; + remittanceInformationUnstructuredArray?: string[]; + proprietaryBankTransactionCode?: string; + balanceAfterTransaction?: { + balanceAmount: { + amount: string; + currency: string; + }; + balanceType: string; + }; + internalTransactionId?: string; + [key: string]: unknown; // Allow additional fields +} + export interface Transaction { internal_transaction_id: string | null; account_id: string; - amount: number; - currency: string; + transaction_value: number; + transaction_currency: string; description: string; - date: string; - status: string; + transaction_date: string; + transaction_status: string; // Optional fields that may be present in some transactions + institution_id?: string; + iban?: string; booking_date?: string; value_date?: string; creditor_name?: string; @@ -34,6 +74,8 @@ export interface Transaction { category?: string; created_at?: string; updated_at?: string; + // Raw transaction data (only present when summary_only=false) + raw_transaction?: RawTransactionData; } // Type for raw transaction data from API (before sanitization)