import { DataTable } from '@/components/ui'; import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; import type { ColumnDef } from '@tanstack/react-table'; import { useCallback, useMemo, useState } from 'react'; import { useExchanges } from '../hooks/useExchanges'; import type { AddProviderMappingDialogState, DeleteDialogState, EditingCell, Exchange } from '../types'; import { formatDate, formatProviderMapping, getProviderMappingColor, sortProviderMappings } from '../utils/formatters'; import { AddProviderMappingDialog } from './AddProviderMappingDialog'; import { DeleteExchangeDialog } from './DeleteExchangeDialog'; export function ExchangesTable() { const { exchanges, loading, error, updateExchange, updateProviderMapping, createProviderMapping, refetch } = useExchanges(); const [editingCell, setEditingCell] = useState(null); const [editValue, setEditValue] = useState(''); const [addProviderDialog, setAddProviderDialog] = useState(null); const [deleteDialog, setDeleteDialog] = useState(null); const handleCellEdit = useCallback( async (id: string, field: string, value: string) => { if (field === 'name' || field === 'code') { await updateExchange(id, { [field]: value }); } setEditingCell(null); setEditValue(''); }, [updateExchange] ); const handleToggleActive = useCallback( async (id: string, currentStatus: boolean) => { await updateExchange(id, { active: !currentStatus }); }, [updateExchange] ); const handleAddProviderMapping = useCallback(async (exchangeId: string, exchangeName: string) => { setAddProviderDialog({ exchangeId, exchangeName }); }, []); const handleDeleteExchange = useCallback((exchangeId: string, exchangeName: string, providerMappingCount: number) => { setDeleteDialog({ exchangeId, exchangeName, providerMappingCount }); }, []); const handleConfirmDelete = useCallback(async (exchangeId: string) => { const success = await updateExchange(exchangeId, { visible: false }); if (success) { refetch(); } return success; }, [updateExchange, refetch]); const handleToggleProviderMapping = useCallback( async (mappingId: string, currentStatus: boolean) => { const success = await updateProviderMapping(mappingId, { active: !currentStatus }); if (success) { // Refresh the main table data to get updated counts and mappings refetch(); } }, [updateProviderMapping, refetch] ); const handleRowExpand = useCallback( async (_row: any) => { // Row expansion is now handled automatically by TanStack Table // No need to fetch data since all mappings are already loaded }, [] ); const columns = useMemo[]>(() => { return [ { id: 'expander', header: '', cell: ({ row }) => { return row.getCanExpand() ? ( ) : null; }, size: 40, enableResizing: false, disableTooltip: true, }, { id: 'code', header: 'Code', accessorKey: 'code', size: 120, cell: ({ getValue, row, cell: _cell }) => { const isEditing = editingCell?.id === row.original.id && editingCell?.field === 'code'; if (isEditing) { return ( setEditValue(e.target.value)} onBlur={() => handleCellEdit(row.original.id, 'code', editValue)} onKeyDown={e => { if (e.key === 'Enter') { handleCellEdit(row.original.id, 'code', editValue); } else if (e.key === 'Escape') { setEditingCell(null); setEditValue(''); } }} className="w-full bg-surface border border-border rounded px-2 py-1 text-sm font-mono" autoFocus /> ); } return (
{ setEditingCell({ id: row.original.id, field: 'code' }); setEditValue(getValue() as string); }} > {getValue() as string}
); }, }, { id: 'name', header: 'Name', accessorKey: 'name', size: 250, cell: ({ getValue, row, cell: _cell }) => { const isEditing = editingCell?.id === row.original.id && editingCell?.field === 'name'; if (isEditing) { return ( setEditValue(e.target.value)} onBlur={() => handleCellEdit(row.original.id, 'name', editValue)} onKeyDown={e => { if (e.key === 'Enter') { handleCellEdit(row.original.id, 'name', editValue); } else if (e.key === 'Escape') { setEditingCell(null); setEditValue(''); } }} className="w-full bg-surface border border-border rounded px-2 py-1 text-sm" autoFocus /> ); } return (
{ setEditingCell({ id: row.original.id, field: 'name' }); setEditValue(getValue() as string); }} > {getValue() as string}
); }, }, { id: 'country', header: 'Country', accessorKey: 'country', size: 80, cell: ({ getValue }) => ( {getValue() as string} ), }, { id: 'currency', header: 'Currency', accessorKey: 'currency', size: 80, cell: ({ getValue }) => ( {getValue() as string} ), }, { id: 'active', header: 'Active', accessorKey: 'active', size: 80, disableTooltip: true, cell: ({ getValue, row }) => { const isActive = getValue() as boolean; return ( ); }, }, { id: 'provider_mappings', header: 'Provider Mappings', accessorKey: 'provider_mapping_count', size: 180, disableTooltip: true, cell: ({ getValue, row }) => { const totalMappings = parseInt(getValue() as string) || 0; const activeMappings = parseInt(row.original.active_mapping_count) || 0; // const _verifiedMappings = parseInt(row.original.verified_mapping_count) || 0; // Get provider mappings directly from the exchange data const mappings = row.original.provider_mappings || []; const sortedMappings = sortProviderMappings(mappings); return (
{totalMappings} total {activeMappings > 0 && ( {activeMappings} active )}
{mappings.length > 0 ? (
{sortedMappings.slice(0, 3).map((mapping, index) => ( {formatProviderMapping(mapping)} ))} {sortedMappings.length > 3 && ( +{sortedMappings.length - 3} more )}
) : (
No mappings
)}
); }, }, { id: 'actions', header: 'Actions', size: 160, disableTooltip: true, cell: ({ row }) => (
), }, { id: 'updated_at', header: 'Last Updated', accessorKey: 'updated_at', size: 120, cell: ({ getValue }) => ( {formatDate(getValue() as string)} ), }, ]; }, [ editingCell, editValue, handleCellEdit, handleToggleActive, handleAddProviderMapping, handleDeleteExchange, handleConfirmDelete, handleRowExpand, ]); if (error) { return (

Error Loading Exchanges

{error}

Make sure the web-api service is running on localhost:2003

); } const renderSubComponent = ({ row }: { row: any }) => { const exchange = row.original as Exchange; const mappings = exchange.provider_mappings || []; if (mappings.length === 0) { return (
No provider mappings found for this exchange.
); } return (

Provider Mappings

{mappings.map((mapping) => (
{mapping.provider.toUpperCase()} {mapping.provider_exchange_code} {mapping.provider_exchange_name} {mapping.country_code && ( {mapping.country_code} )}
Confidence: {mapping.confidence} Created: {formatDate(mapping.created_at)} {mapping.auto_mapped && Auto-mapped}
{mapping.verified && ( )}
))}
); }; return ( <> true} renderSubComponent={renderSubComponent} /> {addProviderDialog && ( setAddProviderDialog(null)} onCreateMapping={async (mappingRequest) => { const result = await createProviderMapping(mappingRequest); if (result) { setAddProviderDialog(null); refetch(); } }} /> )} {deleteDialog && ( setDeleteDialog(null)} onConfirmDelete={handleConfirmDelete} /> )} ); }