diff --git a/apps/web-api/src/routes/exchange.routes.ts b/apps/web-api/src/routes/exchange.routes.ts index 659faec..9c9ec0a 100644 --- a/apps/web-api/src/routes/exchange.routes.ts +++ b/apps/web-api/src/routes/exchange.routes.ts @@ -9,12 +9,13 @@ import { getMongoDBClient } from '@stock-bot/mongodb-client'; const logger = getLogger('exchange-routes'); export const exchangeRoutes = new Hono(); -// Get all exchanges with provider mapping counts +// Get all exchanges with provider mapping counts and mappings exchangeRoutes.get('/', async c => { try { const postgresClient = getPostgreSQLClient(); - const query = ` + // First get all exchanges with counts + const exchangesQuery = ` SELECT e.id, e.code, @@ -34,12 +35,49 @@ exchangeRoutes.get('/', async c => { ORDER BY e.code `; - const result = await postgresClient.query(query); + const exchangesResult = await postgresClient.query(exchangesQuery); + + // Then get all provider mappings + const mappingsQuery = ` + SELECT + pem.*, + e.code as master_exchange_code, + e.name as master_exchange_name + FROM provider_exchange_mappings pem + JOIN exchanges e ON pem.master_exchange_id = e.id + ORDER BY pem.master_exchange_id, pem.provider, pem.provider_exchange_code + `; + const mappingsResult = await postgresClient.query(mappingsQuery); + + // Group mappings by exchange ID + const mappingsByExchange = mappingsResult.rows.reduce((acc, mapping) => { + const exchangeId = mapping.master_exchange_id; + if (!acc[exchangeId]) { + acc[exchangeId] = []; + } + acc[exchangeId].push(mapping); + return acc; + }, {} as Record); + + // Attach mappings to exchanges + const exchangesWithMappings = exchangesResult.rows.map(exchange => { + const mappings = mappingsByExchange[exchange.id] || []; + logger.info('Exchange mapping debug', { + exchangeId: exchange.id, + exchangeCode: exchange.code, + mappingsCount: mappings.length, + availableExchangeIds: Object.keys(mappingsByExchange) + }); + return { + ...exchange, + provider_mappings: mappings + }; + }); return c.json({ success: true, - data: result.rows, - total: result.rows.length, + data: exchangesWithMappings, + total: exchangesWithMappings.length, }); } catch (error) { logger.error('Failed to get exchanges', { error }); @@ -343,6 +381,9 @@ exchangeRoutes.post('/provider-mappings', async c => { ); } + // Validate and clean currency field (must be 3 characters or null) + const cleanCurrency = currency && currency.length <= 3 ? currency : null; + const query = ` INSERT INTO provider_exchange_mappings (provider, provider_exchange_code, provider_exchange_name, master_exchange_id, @@ -357,7 +398,7 @@ exchangeRoutes.post('/provider-mappings', async c => { provider_exchange_name, master_exchange_id, country_code, - currency, + cleanCurrency, confidence, active, verified, diff --git a/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx b/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx index 2b8668e..7a1aec4 100644 --- a/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx +++ b/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx @@ -24,7 +24,6 @@ export function ExchangesTable() { exchangeId: string; exchangeName: string; } | null>(null); - const [expandedRowData, setExpandedRowData] = useState>({}); const handleCellEdit = useCallback( async (id: string, field: string, value: string) => { @@ -62,20 +61,7 @@ export function ExchangesTable() { async (mappingId: string, currentStatus: boolean) => { const success = await updateProviderMapping(mappingId, { active: !currentStatus }); if (success) { - // Update the local expanded row data immediately - setExpandedRowData(prev => { - const updated = { ...prev }; - Object.keys(updated).forEach(exchangeId => { - updated[exchangeId] = updated[exchangeId].map(mapping => - mapping.id === mappingId - ? { ...mapping, active: !currentStatus } - : mapping - ); - }); - return updated; - }); - - // Also refresh the main table data + // Refresh the main table data to get updated counts and mappings refetch(); } }, @@ -84,18 +70,10 @@ export function ExchangesTable() { const handleRowExpand = useCallback( async (row: any) => { - const exchangeId = row.original.id; - if (!expandedRowData[exchangeId]) { - const details = await fetchExchangeDetails(exchangeId); - if (details) { - setExpandedRowData(prev => ({ - ...prev, - [exchangeId]: details.provider_mappings - })); - } - } + // Row expansion is now handled automatically by TanStack Table + // No need to fetch data since all mappings are already loaded }, - [fetchExchangeDetails] + [] ); const columns = useMemo[]>(() => { @@ -258,27 +236,18 @@ export function ExchangesTable() { const activeMappings = parseInt(row.original.active_mapping_count) || 0; const verifiedMappings = parseInt(row.original.verified_mapping_count) || 0; - // Parse provider mappings from the expanded data or from a summary field - const exchangeId = row.original.id; - const mappings = expandedRowData[exchangeId] || []; + // Get provider mappings directly from the exchange data + const mappings = row.original.provider_mappings || []; - const handleLoadMappings = async () => { - if (mappings.length === 0 && totalMappings > 0) { - const details = await fetchExchangeDetails(exchangeId); - if (details) { - setExpandedRowData(prev => ({ - ...prev, - [exchangeId]: details.provider_mappings - })); - } - } - }; + // Sort mappings to show active ones first + const sortedMappings = [...mappings].sort((a, b) => { + if (a.active && !b.active) return -1; + if (!a.active && b.active) return 1; + return 0; + }); return ( -
+
{totalMappings} total @@ -290,18 +259,16 @@ export function ExchangesTable() {
{mappings.length > 0 ? (
- {mappings.slice(0, 3).map((mapping, index) => ( - - {mapping.provider.toLowerCase()} - ({mapping.provider_exchange_code}) + {sortedMappings.slice(0, 3).map((mapping, index) => ( + + {mapping.provider.toLowerCase()} + ({mapping.provider_exchange_code}) ))} - {mappings.length > 3 && ( - +{mappings.length - 3} more + {sortedMappings.length > 3 && ( + +{sortedMappings.length - 3} more )}
- ) : totalMappings > 0 ? ( -
Hover to load mappings...
) : (
No mappings
)} @@ -350,13 +317,11 @@ export function ExchangesTable() { }, [ editingCell, editValue, - expandedRowData, handleCellEdit, handleToggleActive, handleAddProviderMapping, handleDeleteExchange, handleRowExpand, - fetchExchangeDetails, ]); if (error) { @@ -373,7 +338,7 @@ export function ExchangesTable() { const renderSubComponent = ({ row }: { row: any }) => { const exchange = row.original as Exchange; - const mappings = expandedRowData[exchange.id] || []; + const mappings = exchange.provider_mappings || []; if (mappings.length === 0) { return ( diff --git a/apps/web-app/src/features/exchanges/types/index.ts b/apps/web-app/src/features/exchanges/types/index.ts index 1ee1885..3c4904d 100644 --- a/apps/web-app/src/features/exchanges/types/index.ts +++ b/apps/web-app/src/features/exchanges/types/index.ts @@ -30,6 +30,7 @@ export interface Exchange { active_mapping_count: string; verified_mapping_count: string; providers: string | null; + provider_mappings: ProviderMapping[]; } export interface ExchangeDetails {