import { create } from 'zustand'; import { invoke } from '../lib/tauri'; export interface PortfolioSummary { totalValueUsd: number; totalCostBasisUsd: number; totalPnlUsd: number; totalPnlPercent: number; dayChangeUsd: number; dayChangePercent: number; } export interface AssetHolding { asset: string; symbol: string; balance: number; balanceFormatted: string; priceUsd: number; valueUsd: number; costBasisUsd: number; pnlUsd: number; pnlPercent: number; allocationPercent: number; } export interface TaxableTransaction { id: string; txType: 'buy' | 'sell' | 'swap' | 'transfer'; asset: string; amount: number; priceUsd: number; totalUsd: number; costBasisUsd?: number; gainLossUsd?: number; timestamp: number; isLongTerm: boolean; } export interface HistoryPoint { timestamp: number; valueUsd: number; } interface PortfolioState { summary: PortfolioSummary | null; holdings: AssetHolding[]; taxReport: TaxableTransaction[]; history: HistoryPoint[]; isLoading: boolean; error: string | null; } interface PortfolioActions { loadSummary: () => Promise; loadHoldings: () => Promise; loadTaxReport: (year: number) => Promise; exportTaxReport: (year: number, format: 'csv' | 'txf' | 'json') => Promise; loadHistory: (days: number) => Promise; clearError: () => void; } // Transform snake_case to camelCase const transformSummary = (data: Record): PortfolioSummary => ({ totalValueUsd: data.total_value_usd as number, totalCostBasisUsd: data.total_cost_basis_usd as number, totalPnlUsd: data.total_pnl_usd as number, totalPnlPercent: data.total_pnl_percent as number, dayChangeUsd: data.day_change_usd as number, dayChangePercent: data.day_change_percent as number, }); const transformHolding = (data: Record): AssetHolding => ({ asset: data.asset as string, symbol: data.symbol as string, balance: data.balance as number, balanceFormatted: data.balance_formatted as string, priceUsd: data.price_usd as number, valueUsd: data.value_usd as number, costBasisUsd: data.cost_basis_usd as number, pnlUsd: data.pnl_usd as number, pnlPercent: data.pnl_percent as number, allocationPercent: data.allocation_percent as number, }); const transformTaxTx = (data: Record): TaxableTransaction => ({ id: data.id as string, txType: data.tx_type as TaxableTransaction['txType'], asset: data.asset as string, amount: data.amount as number, priceUsd: data.price_usd as number, totalUsd: data.total_usd as number, costBasisUsd: data.cost_basis_usd as number | undefined, gainLossUsd: data.gain_loss_usd as number | undefined, timestamp: data.timestamp as number, isLongTerm: data.is_long_term as boolean, }); const transformHistory = (data: Record): HistoryPoint => ({ timestamp: data.timestamp as number, valueUsd: data.value_usd as number, }); export const usePortfolioStore = create((set) => ({ summary: null, holdings: [], taxReport: [], history: [], isLoading: false, error: null, loadSummary: async () => { try { set({ isLoading: true, error: null }); const data = await invoke>('portfolio_get_summary'); const summary = transformSummary(data); set({ summary, isLoading: false }); } catch (error) { set({ error: String(error), isLoading: false }); } }, loadHoldings: async () => { try { set({ isLoading: true, error: null }); const data = await invoke[]>('portfolio_get_holdings'); const holdings = data.map(transformHolding); set({ holdings, isLoading: false }); } catch (error) { set({ error: String(error), isLoading: false }); } }, loadTaxReport: async (year: number) => { try { set({ isLoading: true, error: null }); const data = await invoke[]>('portfolio_get_tax_report', { year }); const taxReport = data.map(transformTaxTx); set({ taxReport, isLoading: false }); } catch (error) { set({ error: String(error), isLoading: false }); } }, exportTaxReport: async (year: number, format: 'csv' | 'txf' | 'json') => { try { const data = await invoke('portfolio_export_tax_report', { year, format }); return data; } catch (error) { set({ error: String(error) }); throw error; } }, loadHistory: async (days: number) => { try { set({ isLoading: true, error: null }); const data = await invoke[]>('portfolio_get_history', { days }); const history = data.map(transformHistory); set({ history, isLoading: false }); } catch (error) { set({ error: String(error), isLoading: false }); } }, clearError: () => set({ error: null }), })); // Helper to format currency export const formatUSD = (value: number): string => { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(value); }; // Helper to format percentage export const formatPercent = (value: number): string => { const sign = value >= 0 ? '+' : ''; return `${sign}${value.toFixed(2)}%`; }; // Alias for formatting USD export const formatCurrency = formatUSD; // Helper to format sompi to SYN export const formatAmount = (sompi: number): string => { const syn = sompi / 100_000_000; return `${syn.toFixed(8)} SYN`; };