'use client' import { useState, useEffect } from 'react' import EditExpenseModal from './EditExpenseModal' import ReceiptViewer from './ReceiptViewer' interface ExpenseSplit { participant_id?: number participant_name: string percentage: number amount: number is_settled: boolean } interface Expense { id: number description: string amount: number currency_code: string amount_in_trip_currency: number category: string | null paid_by_id: number paid_by_name: string exchange_rate: number date_incurred: string created_at: string splits: ExpenseSplit[] receipt_url?: string } interface Participant { id: number name: string is_creator: boolean } interface ExpenseListProps { tripId: number refreshTrigger?: number participants: Participant[] tripCurrency: string onExpenseUpdated?: () => void } const commonCategories = { 'Food & Dining': '🍔', 'Transportation': '🚗', 'Accommodation': '🏨', 'Entertainment': '🎬', 'Shopping': '🛍️', 'Healthcare': '🏥', 'Education': '📚', 'Business': '💼', 'Other': '📝' } const getCategoryIcon = (category: string | null) => { if (!category) return '📝' return commonCategories[category as keyof typeof commonCategories] || '📝' } const formatDate = (dateString: string) => { const date = new Date(dateString) return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) } const formatDateOnly = (dateString: string) => { const date = new Date(dateString) const today = new Date() const yesterday = new Date(today) yesterday.setDate(yesterday.getDate() - 1) // Check if it's today if (date.toDateString() === today.toDateString()) { return 'Today' } // Check if it's yesterday if (date.toDateString() === yesterday.toDateString()) { return 'Yesterday' } // Otherwise return formatted date return date.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: date.getFullYear() !== today.getFullYear() ? 'numeric' : undefined }) } const formatTime = (dateString: string) => { const date = new Date(dateString) return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) } const getCurrencySymbol = (currencyCode: string) => { const symbols: { [key: string]: string } = { 'USD': '$', 'EUR': '€', 'GBP': '£', 'JPY': '¥', 'CNY': '¥', 'INR': '₹', 'AED': 'د.إ', } return symbols[currencyCode] || currencyCode } // Get display percentage - always show the base equal split for visual harmony const getDisplayPercentage = (splitsCount: number) => { return Math.floor(100 / splitsCount) } // Type for expense when editing (matches EditExpenseModal's expected format) type ExpenseForEdit = { id: number description: string amount: number currency_code: string exchange_rate: number category?: string paid_by_id: number date_incurred: string receipt_url?: string splits: Array<{ participant_id: number percentage: number amount: number }> } export default function ExpenseList({ tripId, refreshTrigger, participants, tripCurrency, onExpenseUpdated }: ExpenseListProps) { const [expenses, setExpenses] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [editingExpense, setEditingExpense] = useState(null) const [showReceiptViewer, setShowReceiptViewer] = useState(false) const [selectedReceiptUrl, setSelectedReceiptUrl] = useState('') useEffect(() => { loadExpenses() }, [tripId, refreshTrigger]) const loadExpenses = async () => { try { setLoading(true) setError('') // Use relative URL to leverage Next.js API rewrite (works on mobile) const { getAuthHeaders } = await import('@/lib/trip-auth') const response = await fetch(`/api/expenses/${tripId}/expenses`, { headers: getAuthHeaders(tripId), }) if (response.ok) { const data = await response.json() setExpenses(data) return } else { throw new Error('Failed to load expenses') } } catch (err) { setError('Failed to load expenses') console.error(err) } finally { setLoading(false) } } if (loading) { return (
{[1, 2, 3].map((i) => (
))}
) } if (error) { return (

{error}

) } if (expenses.length === 0) { return (

No expenses yet

Start tracking expenses by adding your first expense.

) } // Handle receipt viewing const handleViewReceipt = (receiptUrl: string) => { setSelectedReceiptUrl(receiptUrl) setShowReceiptViewer(true) } // Calculate totals const totalInTripCurrency = expenses.reduce((sum, expense) => sum + expense.amount_in_trip_currency, 0) // Group expenses by day const expensesByDay = expenses.reduce((groups, expense) => { const date = new Date(expense.date_incurred) const dateKey = date.toDateString() if (!groups[dateKey]) { groups[dateKey] = [] } groups[dateKey].push(expense) return groups }, {} as Record) // Sort days (most recent first) const sortedDays = Object.keys(expensesByDay).sort((a, b) => { return new Date(b).getTime() - new Date(a).getTime() }) // Sort expenses within each day (most recent first) sortedDays.forEach(day => { expensesByDay[day].sort((a, b) => { return new Date(b.date_incurred).getTime() - new Date(a.date_incurred).getTime() }) }) return (
{/* Summary Card */}

Total Expenses

{getCurrencySymbol(tripCurrency)}{totalInTripCurrency.toFixed(2)}

{expenses.length} expense{expenses.length !== 1 ? 's' : ''}

{/* Expenses List Grouped by Day */}
{sortedDays.map((dayKey) => { const dayExpenses = expensesByDay[dayKey] const dayTotal = dayExpenses.reduce((sum, expense) => sum + expense.amount_in_trip_currency, 0) return (
{/* Day Header */}

{formatDateOnly(dayExpenses[0].date_incurred)}

{getCurrencySymbol(tripCurrency)}{dayTotal.toFixed(2)} • {dayExpenses.length} expense{dayExpenses.length !== 1 ? 's' : ''}

{/* Expenses for this day */} {dayExpenses.map((expense) => (
{getCategoryIcon(expense.category)} {/* Receipt thumbnail */} {expense.receipt_url && ( )}

{expense.description}

Paid by {expense.paid_by_name} {formatTime(expense.date_incurred)}

{getCurrencySymbol(expense.currency_code)}{expense.amount.toFixed(2)}

{expense.currency_code !== tripCurrency && (

{getCurrencySymbol(tripCurrency)}{expense.amount_in_trip_currency.toFixed(2)}

)}
))}
) })}
{/* Edit Expense Modal */} setEditingExpense(null)} expense={editingExpense} participants={participants} tripCurrency={tripCurrency} tripId={tripId} onExpenseUpdated={() => { setEditingExpense(null) loadExpenses() // Notify parent component to refresh other components if (onExpenseUpdated) { onExpenseUpdated() } }} /> {/* Receipt Viewer Modal */} {showReceiptViewer && selectedReceiptUrl && ( { setShowReceiptViewer(false) setSelectedReceiptUrl('') }} title="Receipt" showDownload={true} /> )}
) }