'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Edit, Loader2, Plus, RefreshCw, Trash2 } from 'lucide-react'; import DataTable from '@/components/ui/DataTable'; import Modal from '@/components/ui/Modal'; import ActionButton from '@/components/ui/ActionButton'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { apiClient } from '@/lib/api-client'; interface CurrencyRate { id: string; code: string; name: string; symbol: string; baseCurrencyCode: string; exchangeRate: number; isActive: boolean; createdAt: string; } const CURRENCY_META: Record = { ETB: { name: 'Ethiopian Birr', symbol: 'Br' }, DJF: { name: 'Djiboutian Franc', symbol: 'Fdj' }, USD: { name: 'US Dollar', symbol: '$' }, }; export default function CurrenciesPage() { const [editingRate, setEditingRate] = useState(null); const [rateInput, setRateInput] = useState(''); const [error, setError] = useState(null); const [showAddModal, setShowAddModal] = useState(false); const [addForm, setAddForm] = useState({ code: '', name: '', symbol: '', exchangeRate: '' }); const [deleteConfirm, setDeleteConfirm] = useState(null); const queryClient = useQueryClient(); const { data: currencies = [], isLoading } = useQuery({ queryKey: ['currencies'], queryFn: () => apiClient.get('/currencies'), }); const updateMutation = useMutation({ mutationFn: ({ id, exchangeRate }: { id: string; exchangeRate: number }) => apiClient.patch(`/currencies/${id}`, { exchangeRate }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['currencies'] }); setEditingRate(null); setError(null); }, onError: (err: any) => { setError(err.response?.data?.message || 'Failed to update exchange rate'); }, }); const createMutation = useMutation({ mutationFn: (data: any) => apiClient.post('/currencies', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['currencies'] }); setShowAddModal(false); setAddForm({ code: '', name: '', symbol: '', exchangeRate: '' }); setError(null); }, onError: (err: any) => setError(err.response?.data?.message || 'Failed to add currency'), }); const deleteMutation = useMutation({ mutationFn: (id: string) => apiClient.delete(`/currencies/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['currencies'] }); setDeleteConfirm(null); }, onError: (err: any) => setError(err.response?.data?.message || 'Failed to delete currency'), }); const syncMutation = useMutation({ mutationFn: () => apiClient.post('/currencies/sync-rates', {}), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['currencies'] }), onError: (err: any) => setError(err.response?.data?.message || 'Failed to sync rates'), }); const handleEdit = (currency: CurrencyRate) => { setEditingRate(currency); setRateInput(currency.exchangeRate.toString()); setError(null); }; const handleSave = async () => { const rate = parseFloat(rateInput); if (isNaN(rate) || rate <= 0) { setError('Exchange rate must be a positive number'); return; } await updateMutation.mutateAsync({ id: editingRate!.id, exchangeRate: rate }); }; const currenciesArray = Array.isArray(currencies) ? currencies : (currencies as any)?.items ?? []; const columns = [ { key: 'code', label: 'Currency', render: (c: CurrencyRate) => (
{CURRENCY_META[c.code]?.symbol ?? c.symbol}
{c.code}
{CURRENCY_META[c.code]?.name ?? c.name}
), }, { key: 'baseCurrencyCode', label: 'Base', render: (c: CurrencyRate) => ( {c.baseCurrencyCode} ), }, { key: 'exchangeRate', label: 'Exchange Rate', render: (c: CurrencyRate) => (
1 {c.baseCurrencyCode} = {c.exchangeRate} {c.code}
1 {c.code} = {(1 / c.exchangeRate).toFixed(6)} {c.baseCurrencyCode}
), }, { key: 'createdAt', label: 'Last Updated', render: (c: CurrencyRate) => ( {new Date(c.createdAt).toLocaleDateString()} ), }, ]; const actions = [ { label: 'Edit', onClick: handleEdit, variant: 'secondary' as const, icon: Edit }, { label: 'Delete', onClick: (c: CurrencyRate) => setDeleteConfirm(c), variant: 'danger' as const, icon: Trash2 }, ]; return (

Exchange Rates

Manage ETB exchange rates for display currencies (DJF, USD)

{ setError(null); setShowAddModal(true); }}>Add Currency syncMutation.mutate()} loading={syncMutation.isPending} > Sync Rates
{error && !editingRate && (
{error}
)}
{(['ETB', 'DJF', 'USD'] as const).map((code) => { const entry = currenciesArray.find((c: CurrencyRate) => c.code === code); return (
{CURRENCY_META[code].name}
{code}
{entry ? ( <>
{entry.exchangeRate}
per ETB
) : ( Not configured )}
); })}
{isLoading ? (
) : ( )}
{ setShowAddModal(false); setError(null); }} title="Add Currency" size="sm" >
{error && (
{error}
)}
setAddForm({ ...addForm, code: e.target.value.toUpperCase() })} />
setAddForm({ ...addForm, symbol: e.target.value })} />
setAddForm({ ...addForm, name: e.target.value })} />
setAddForm({ ...addForm, exchangeRate: e.target.value })} />
{ setShowAddModal(false); setError(null); }}>Cancel { if (!addForm.code || !addForm.name || !addForm.symbol || !addForm.exchangeRate) { setError('All fields are required'); return; } const rate = parseFloat(addForm.exchangeRate); if (isNaN(rate) || rate <= 0) { setError('Exchange rate must be a positive number'); return; } createMutation.mutate({ code: addForm.code, name: addForm.name, symbol: addForm.symbol, exchangeRate: rate }); }} > Add Currency
setDeleteConfirm(null)} onConfirm={() => deleteMutation.mutate(deleteConfirm!.id)} title="Delete Currency" message={`Delete ${deleteConfirm?.code} (${CURRENCY_META[deleteConfirm?.code ?? '']?.name ?? deleteConfirm?.code})? This will remove the exchange rate record.`} confirmText="Delete" isDanger isLoading={deleteMutation.isPending} /> { setEditingRate(null); setError(null); }} title={`Update Rate — ${editingRate?.code}`} size="sm" >
{error && (
{error}
)}
Currency: {editingRate?.code} — {CURRENCY_META[editingRate?.code ?? '']?.name}
setRateInput(e.target.value)} className="input w-full" placeholder="e.g., 3.25" autoFocus /> {rateInput && parseFloat(rateInput) > 0 && (

≈ 1 {editingRate?.code} = {(1 / parseFloat(rateInput)).toFixed(6)} {editingRate?.baseCurrencyCode}

)}
{ setEditingRate(null); setError(null); }}> Cancel Save Rate
); }