diff --git a/backend/src/services/apiServer.ts b/backend/src/services/apiServer.ts index 2eda25d..8483b88 100644 --- a/backend/src/services/apiServer.ts +++ b/backend/src/services/apiServer.ts @@ -18,6 +18,7 @@ import { deleteTradeProfileForUser, ensureDefaultTradeProfileForUser, getCurrentUserProfile, + listAllTradeProfiles, listTradeProfilesForUser, saveTradeProfileForUser, } from './profileRepository.js'; @@ -1644,9 +1645,22 @@ export class ApiServer { try { const ensureDefault = String(req.query.ensureDefault || '').toLowerCase() === 'true'; - const profiles = ensureDefault - ? await ensureDefaultTradeProfileForUser(authUserId, supabaseService) - : await listTradeProfilesForUser(authUserId, supabaseService); + const scope = String(req.query.scope || 'user').toLowerCase(); + const wantsAll = scope === 'all'; + const isAdmin = wantsAll ? await isTradingAdmin(authUserId, (req as AuthenticatedRequest).authRole) : false; + + let profiles; + if (ensureDefault && !wantsAll) { + profiles = await ensureDefaultTradeProfileForUser(authUserId, supabaseService); + } else if (wantsAll) { + if (!isAdmin) { + res.status(403).json({ error: 'Forbidden: Admin role required' }); + return; + } + profiles = await listAllTradeProfiles(supabaseService); + } else { + profiles = await listTradeProfilesForUser(authUserId, supabaseService); + } res.json({ profiles }); } catch (error: any) { res.status(500).json({ error: `Failed to load profiles: ${error.message}` }); diff --git a/backend/src/services/profileRepository.ts b/backend/src/services/profileRepository.ts index 0486551..cba4d68 100644 --- a/backend/src/services/profileRepository.ts +++ b/backend/src/services/profileRepository.ts @@ -123,6 +123,29 @@ async function listProfilesFromCosmos(userId: string): Promise Boolean(profile)); } +async function listAllProfilesFromCosmos(): Promise { + if (!isCosmosConfigured()) { + return []; + } + + const container = getContainer(PROFILE_CONTAINER); + const { resources } = await container.items.query({ + query: 'SELECT * FROM c WHERE c.productId = @productId AND c.type = @type ORDER BY c.createdAt DESC', + parameters: [ + { name: '@productId', value: config.PRODUCT_ID }, + { name: '@type', value: 'trade_profile' }, + ], + }).fetchAll(); + + return resources + .map((resource) => normalizeProfile({ + ...resource, + created_at: resource.createdAt, + updated_at: resource.updatedAt, + })) + .filter((profile): profile is TradeProfileRecord => Boolean(profile)); +} + async function listProfilesFromSupabase(userId: string, legacyService?: LegacySupabaseService): Promise { const client = legacyService?.getClient?.(); if (!client) { @@ -149,6 +172,31 @@ async function listProfilesFromSupabase(userId: string, legacyService?: LegacySu } } +async function listAllProfilesFromSupabase(legacyService?: LegacySupabaseService): Promise { + const client = legacyService?.getClient?.(); + if (!client) { + return []; + } + + try { + const { data, error } = await client + .from('trade_profiles') + .select('id,user_id,name,allocated_capital,risk_per_trade_percent,symbols,is_active,strategy_config,created_at,updated_at') + .order('created_at', { ascending: false }); + + if (error || !Array.isArray(data)) { + return []; + } + + return data + .map((row) => normalizeProfile(row as TradeProfileRecord)) + .filter((profile): profile is TradeProfileRecord => Boolean(profile)); + } catch (error) { + logger.warn(`[Profiles] Legacy global profile read failed: ${error instanceof Error ? error.message : 'unknown error'}`); + return []; + } +} + async function mirrorProfileToSupabase(profile: TradeProfileRecord, legacyService?: LegacySupabaseService): Promise { const client = legacyService?.getClient?.(); if (!client) return; @@ -202,6 +250,19 @@ export async function listTradeProfilesForUser(userId: string, legacyService?: L return listProfilesFromSupabase(userId, legacyService); } +export async function listAllTradeProfiles(legacyService?: LegacySupabaseService): Promise { + try { + const cosmosProfiles = await listAllProfilesFromCosmos(); + if (cosmosProfiles.length > 0) { + return cosmosProfiles; + } + } catch (error) { + logger.warn(`[Profiles] Cosmos global profile read failed, falling back to legacy store: ${error instanceof Error ? error.message : 'unknown error'}`); + } + + return listAllProfilesFromSupabase(legacyService); +} + export async function ensureDefaultTradeProfileForUser(userId: string, legacyService?: LegacySupabaseService): Promise { const profiles = await listTradeProfilesForUser(userId, legacyService); if (profiles.length > 0) { diff --git a/web/src/lib/profileApi.ts b/web/src/lib/profileApi.ts index 392972c..8350211 100644 --- a/web/src/lib/profileApi.ts +++ b/web/src/lib/profileApi.ts @@ -63,8 +63,15 @@ export async function fetchCurrentUserProfile(): Promise { return response.profile; } -export async function fetchTradeProfiles(options?: { ensureDefault?: boolean }): Promise { - const query = options?.ensureDefault ? '?ensureDefault=true' : ''; +export async function fetchTradeProfiles(options?: { ensureDefault?: boolean; scope?: 'user' | 'all' }): Promise { + const params = new URLSearchParams(); + if (options?.ensureDefault) { + params.set('ensureDefault', 'true'); + } + if (options?.scope === 'all') { + params.set('scope', 'all'); + } + const query = params.toString() ? `?${params.toString()}` : ''; const response = await apiRequest<{ profiles: TradeProfilePayload[] }>(`/api/profiles${query}`); return Array.isArray(response.profiles) ? response.profiles : []; } diff --git a/web/src/tabs/BacktestTab.tsx b/web/src/tabs/BacktestTab.tsx index 8e1be4d..ababfdf 100644 --- a/web/src/tabs/BacktestTab.tsx +++ b/web/src/tabs/BacktestTab.tsx @@ -1,10 +1,9 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { supabase } from '../lib/supabaseClient'; -import { tableNameProfiles } from '../lib/const'; import { useAuth } from '../components/AuthContext'; import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel'; import { BacktestComparePanel } from '../backtest/components/BacktestComparePanel'; import { useBacktestFeatureGate } from '../backtest/useBacktestFeatureGate'; +import { fetchTradeProfiles } from '../lib/profileApi'; interface BacktestTabProps { previewAsCustomer?: boolean; @@ -31,20 +30,16 @@ export const BacktestTab: React.FC = ({ previewAsCustomer = fa return; } setLoadingProfiles(true); - const { data, error } = await supabase - .from(tableNameProfiles) - .select('*') - .eq('user_id', user.id) - .order('created_at', { ascending: false }); - if (error) { - console.error('[BacktestTab] Failed to fetch profiles:', error); - setProfiles([]); - } else { + try { + const data = await fetchTradeProfiles(); const nextProfiles = data || []; setProfiles(nextProfiles); if (!nextProfiles.find((item: any) => item.id === selectedProfileId)) { setSelectedProfileId(nextProfiles[0]?.id || ''); } + } catch (error) { + console.error('[BacktestTab] Failed to fetch profiles:', error); + setProfiles([]); } setLoadingProfiles(false); }; diff --git a/web/src/tabs/HistoryTab.dom.test.tsx b/web/src/tabs/HistoryTab.dom.test.tsx index 6b42c8e..d9b4d77 100644 --- a/web/src/tabs/HistoryTab.dom.test.tsx +++ b/web/src/tabs/HistoryTab.dom.test.tsx @@ -3,14 +3,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HistoryTab } from './HistoryTab'; -import { tableNameProfiles, tableNameTransactions } from '../lib/const'; +import { tableNameTransactions } from '../lib/const'; -const { authState } = vi.hoisted(() => ({ +const { authState, fetchTradeProfilesMock } = vi.hoisted(() => ({ authState: { user: { id: 'u1' }, profile: { role: 'user' }, refreshProfile: vi.fn() - } + }, + fetchTradeProfilesMock: vi.fn() })); vi.mock('../components/AuthContext', () => ({ @@ -34,6 +35,10 @@ vi.mock('../lib/supabaseClient', () => ({ supabase: { from: vi.fn() } })); +vi.mock('../lib/profileApi', () => ({ + fetchTradeProfiles: fetchTradeProfilesMock +})); + import { supabase } from '../lib/supabaseClient'; describe('HistoryTab Master Suite', () => { @@ -43,9 +48,9 @@ describe('HistoryTab Master Suite', () => { beforeEach(() => { vi.clearAllMocks(); + fetchTradeProfilesMock.mockResolvedValue([{ id: 'p1', name: 'Alpha' }]); (supabase.from as any).mockImplementation((table: string) => { if (table === tableNameTransactions) return mockSupabaseChain(historyData); - if (table === tableNameProfiles) return mockSupabaseChain([{ id: 'p1', name: 'Alpha' }]); return mockSupabaseChain([]); }); }); @@ -85,7 +90,6 @@ describe('HistoryTab Master Suite', () => { (supabase.from as any).mockImplementation((table: string) => { if (table === tableNameTransactions) return mockSupabaseChain(manyRecords); - if (table === tableNameProfiles) return mockSupabaseChain([{ id: 'p1', name: 'Alpha' }]); return mockSupabaseChain([]); }); diff --git a/web/src/tabs/HistoryTab.tsx b/web/src/tabs/HistoryTab.tsx index 6ca03aa..7ddd9e7 100644 --- a/web/src/tabs/HistoryTab.tsx +++ b/web/src/tabs/HistoryTab.tsx @@ -1,12 +1,13 @@ import { useEffect, useState, useMemo } from 'react'; import { supabase } from '../lib/supabaseClient'; import { useAuth } from '../components/AuthContext'; -import { tableNameTransactions, tableNameProfiles, tableNameOrders } from '../lib/const'; +import { tableNameTransactions, tableNameOrders } from '../lib/const'; import { aggregateHistoryLedger, buildHistoryLedger } from '../lib/tradeHistoryLedger'; import { type LifecycleOrderRow } from '../lib/orderLifecycleLedger'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; +import { fetchTradeProfiles } from '../lib/profileApi'; interface TradeRecord { @@ -353,18 +354,16 @@ export const HistoryTab = ({ botState }: HistoryTabProps) => { historyData = histDataLegacy as TradeRecord[] | null; } - let profilesQuery = supabase - .from(tableNameProfiles) - .select('id, name, allocated_capital') - .order('name', { ascending: true }); - - if (profile?.role !== 'admin') { - profilesQuery = profilesQuery.eq('user_id', user.id); - } - - const { data: profData, error: profError } = await profilesQuery; - if (profError) { - console.error('[HistoryTab] Failed loading profiles:', profError.message); + let profData: Array<{ id: string; name: string; allocated_capital?: number }> = []; + try { + const profiles = await fetchTradeProfiles({ scope: profile?.role === 'admin' ? 'all' : 'user' }); + profData = profiles.map((item: any) => ({ + id: String(item.id), + name: String(item.name || item.id || 'Unnamed Profile'), + allocated_capital: Number(item.allocated_capital || 0) + })); + } catch (error: any) { + console.error('[HistoryTab] Failed loading profiles:', error.message || error); } const orderTagColumnsV2 = 'order_id,trade_id,profile_id,sub_tag,timestamp,filled_at,created_at,symbol,side,action,qty,quantity,price,status,source'; diff --git a/web/src/tabs/OverviewTab.dom.test.tsx b/web/src/tabs/OverviewTab.dom.test.tsx index d30dc3e..2aaa7fc 100644 --- a/web/src/tabs/OverviewTab.dom.test.tsx +++ b/web/src/tabs/OverviewTab.dom.test.tsx @@ -3,27 +3,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import { OverviewTab, dedupeLivePositions } from './OverviewTab'; import { DEFAULT_BOT_STATE } from '../hooks/useWebSocket'; -import { tableNameProfiles } from '../lib/const'; - -const { authMock, supabaseMock, createMockQuery, canonicalLifecycleHookState } = vi.hoisted(() => { - const createMockQuery = (data: any, error: any = null) => { - const query: any = { - select: vi.fn(), eq: vi.fn(), order: vi.fn(), limit: vi.fn(), then: vi.fn() - }; - query.select.mockReturnValue(query); - query.eq.mockReturnValue(query); - query.order.mockReturnValue(query); - query.limit.mockReturnValue(query); - query.then.mockImplementation((cb: any) => Promise.resolve({ data, error }).then(cb)); - return query; - }; +const { authMock, fetchTradeProfilesMock, canonicalLifecycleHookState } = vi.hoisted(() => { return { - createMockQuery, authMock: { user: { id: 'u1' }, profile: { role: 'user' } }, - supabaseMock: { - from: vi.fn() - }, + fetchTradeProfilesMock: vi.fn(), canonicalLifecycleHookState: { snapshot: null as any, loading: false, @@ -39,8 +23,8 @@ vi.mock('../components/AuthContext', () => ({ }) })); -vi.mock('../lib/supabaseClient', () => ({ - supabase: supabaseMock +vi.mock('../lib/profileApi', () => ({ + fetchTradeProfiles: fetchTradeProfilesMock })); vi.mock('../hooks/useCanonicalLifecycle', () => ({ @@ -70,13 +54,9 @@ describe('OverviewTab coverage maximization', () => { profileSignals: { 'p1': { signal: 'BUY', passed: true } } } }; - - supabaseMock.from.mockImplementation((table: string) => { - if (table === tableNameProfiles) return createMockQuery([ - { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 5000, strategy_config: { execution: { cooldownMinutes: 5 } } } - ]); - return createMockQuery([]); - }); + fetchTradeProfilesMock.mockResolvedValue([ + { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 5000, strategy_config: { execution: { cooldownMinutes: 5 } } } + ]); }); it('covers all uptime and duration formatting paths', async () => { @@ -99,7 +79,7 @@ describe('OverviewTab coverage maximization', () => { }); it('covers fallback capital logic', async () => { - supabaseMock.from.mockImplementation(() => createMockQuery([])); + fetchTradeProfilesMock.mockResolvedValue([]); mockBotState.settings.totalCapital = 25000; render(); // The label in bot-status-bar is "Allocated:" @@ -107,22 +87,16 @@ describe('OverviewTab coverage maximization', () => { }); it('covers Signal Active branches', async () => { - supabaseMock.from.mockImplementation((table: string) => { - if (table === tableNameProfiles) return createMockQuery([ - { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 0, strategy_config: { execution: { cooldownMinutes: 5 } } } - ]); - return createMockQuery([]); - }); + fetchTradeProfilesMock.mockResolvedValueOnce([ + { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 0, strategy_config: { execution: { cooldownMinutes: 5 } } } + ]); const { unmount } = render(); await waitFor(() => expect(screen.getByText(/Signal active, no allocation/i)).toBeInTheDocument()); unmount(); - supabaseMock.from.mockImplementation((table: string) => { - if (table === tableNameProfiles) return createMockQuery([ - { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 5000, strategy_config: { execution: { cooldownMinutes: 5 } } } - ]); - return createMockQuery([]); - }); + fetchTradeProfilesMock.mockResolvedValueOnce([ + { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 5000, strategy_config: { execution: { cooldownMinutes: 5 } } } + ]); render(); await waitFor(() => expect(screen.getByText(/Signal active, waiting entry/i)).toBeInTheDocument()); }); @@ -225,12 +199,12 @@ describe('OverviewTab coverage maximization', () => { it('covers Admin role data fetching', async () => { authMock.profile.role = 'admin'; render(); - await waitFor(() => expect(supabaseMock.from).toHaveBeenCalled()); + await waitFor(() => expect(fetchTradeProfilesMock).toHaveBeenCalledWith({ scope: 'all' })); }); it('covers error handling in fetchData', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); - supabaseMock.from.mockImplementation(() => { throw new Error('Query Fail'); }); + fetchTradeProfilesMock.mockRejectedValueOnce(new Error('Query Fail')); render(); await waitFor(() => expect(consoleSpy).toHaveBeenCalled()); consoleSpy.mockRestore(); diff --git a/web/src/tabs/OverviewTab.tsx b/web/src/tabs/OverviewTab.tsx index 2d65c6b..5a86fa2 100644 --- a/web/src/tabs/OverviewTab.tsx +++ b/web/src/tabs/OverviewTab.tsx @@ -1,10 +1,9 @@ import { useState, useEffect, useMemo } from 'react'; import type { BotState } from '../hooks/useWebSocket'; -import { supabase } from '../lib/supabaseClient'; -import { tableNameProfiles } from '../lib/const'; import { useAuth } from '../components/AuthContext'; import { aggregateCanonicalLifecycleTrades } from '../lib/orderLifecycleLedger'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; +import { fetchTradeProfiles } from '../lib/profileApi'; interface OverviewTabProps { botState: BotState; @@ -133,19 +132,7 @@ export const OverviewTab = ({ botState, previewAsCustomer = false, connected = t if (cancelled) return; try { - let profilesQuery = supabase - .from(tableNameProfiles) - .select('id, name, allocated_capital, is_active, strategy_config'); - - if (profile?.role !== 'admin') { - profilesQuery = profilesQuery.eq('user_id', user.id); - } - - const { data: profilesData, error: profilesError } = await profilesQuery; - - if (profilesError) { - console.error('[OverviewTab] Failed loading profiles:', profilesError.message); - } + const profilesData = await fetchTradeProfiles({ scope: profile?.role === 'admin' ? 'all' : 'user' }); const activeProfileRows: ActiveProfileCapital[] = ((profilesData as any[]) || []) .filter((p: any) => Boolean(p.is_active)) diff --git a/web/src/tabs/PositionsTab.dom.test.tsx b/web/src/tabs/PositionsTab.dom.test.tsx index 418d49c..0f45cad 100644 --- a/web/src/tabs/PositionsTab.dom.test.tsx +++ b/web/src/tabs/PositionsTab.dom.test.tsx @@ -4,7 +4,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { PositionsTab } from './PositionsTab'; import type { BotState } from '../hooks/useWebSocket'; -import { tableNameOrders, tableNameProfiles, tableNameStocks } from '../lib/const'; +import { tableNameOrders, tableNameStocks } from '../lib/const'; const { authState, @@ -13,7 +13,7 @@ const { entriesEqMock, ordersEqMock, historyEqMock, - profilesEqMock + fetchTradeProfilesMock } = vi.hoisted(() => ({ authState: { user: { id: 'user-1' } as any, @@ -24,7 +24,7 @@ const { entriesEqMock: vi.fn(), ordersEqMock: vi.fn(), historyEqMock: vi.fn(), - profilesEqMock: vi.fn() + fetchTradeProfilesMock: vi.fn() })); vi.mock('../components/AuthContext', () => ({ @@ -40,12 +40,16 @@ vi.mock('../lib/supabaseClient', () => ({ } })); +vi.mock('../lib/profileApi', () => ({ + fetchTradeProfiles: fetchTradeProfilesMock +})); + interface QueryResult { data: any; error: any; } -type TableKey = 'entries' | 'orders' | 'trade_history' | 'trade_profiles'; +type TableKey = 'entries' | 'orders' | 'trade_history'; const makeBuilder = (result: QueryResult, eqSpy: (field: string, value: any) => void) => { const builder: any = { @@ -65,8 +69,7 @@ const configureQueries = (plan: Record) => { const queues = { entries: [...plan.entries], orders: [...plan.orders], - trade_history: [...plan.trade_history], - trade_profiles: [...plan.trade_profiles] + trade_history: [...plan.trade_history] }; fromMock.mockImplementation((table: string) => { @@ -79,9 +82,6 @@ const configureQueries = (plan: Record) => { if (table === 'trade_history') { return makeBuilder(queues.trade_history.shift() || { data: [], error: null }, historyEqMock); } - if (table === tableNameProfiles) { - return makeBuilder(queues.trade_profiles.shift() || { data: [], error: null }, profilesEqMock); - } return makeBuilder({ data: [], error: null }, vi.fn()); }); }; @@ -262,7 +262,11 @@ describe('PositionsTab DOM behavior', () => { entriesEqMock.mockReset(); ordersEqMock.mockReset(); historyEqMock.mockReset(); - profilesEqMock.mockReset(); + fetchTradeProfilesMock.mockReset(); + fetchTradeProfilesMock.mockResolvedValue([ + { id: 'p1', name: 'High Risk Scalper' }, + { id: 'p2', name: 'Conservative Bag' } + ]); vi.stubGlobal('confirm', vi.fn(() => true)); vi.stubGlobal('alert', vi.fn()); @@ -361,13 +365,6 @@ describe('PositionsTab DOM behavior', () => { trade_history: [{ data: [{ trade_id: 'TRD-BOT-3', profile_id: 'p2' }, { trade_id: '', profile_id: 'p1' }], error: null - }], - trade_profiles: [{ - data: [ - { id: 'p1', name: 'High Risk Scalper' }, - { id: 'p2', name: 'Conservative Bag' } - ], - error: null }] }); @@ -449,7 +446,6 @@ describe('PositionsTab DOM behavior', () => { error: null }], trade_history: [{ data: [], error: null }], - trade_profiles: [{ data: [{ id: 'p1', name: 'High Risk Scalper' }], error: null }] }); const user = userEvent.setup(); @@ -507,9 +503,9 @@ describe('PositionsTab DOM behavior', () => { { data: null, error: { message: 'v2 fallback failed' } }, { data: null, error: { message: 'legacy failed' } } ], - trade_history: [{ data: null, error: { message: 'history failed' } }], - trade_profiles: [{ data: null, error: { message: 'profiles failed' } }] + trade_history: [{ data: null, error: { message: 'history failed' } }] }); + fetchTradeProfilesMock.mockRejectedValueOnce(new Error('profiles failed')); render( { expect(entriesEqMock).toHaveBeenCalledWith('user_id', 'user-2'); expect(ordersEqMock).toHaveBeenCalledWith('user_id', 'user-2'); expect(historyEqMock).toHaveBeenCalledWith('user_id', 'user-2'); - expect(profilesEqMock).toHaveBeenCalledWith('user_id', 'user-2'); + expect(fetchTradeProfilesMock).toHaveBeenCalledWith({ scope: 'user' }); expect(warnSpy).toHaveBeenCalledWith( '[PositionsTab] V3 order query failed, falling back to v2 columns:', diff --git a/web/src/tabs/PositionsTab.tsx b/web/src/tabs/PositionsTab.tsx index 0c3d321..55afb23 100644 --- a/web/src/tabs/PositionsTab.tsx +++ b/web/src/tabs/PositionsTab.tsx @@ -1,11 +1,12 @@ import { useEffect, useMemo, useState } from 'react'; -import type { BotState } from '../hooks/useWebSocket'; +import type { BotState } from '../hooks/useWebSocket'; import { supabase } from '../lib/supabaseClient'; import { tradingRuntime } from '../lib/runtime'; import { useAuth } from '../components/AuthContext'; -import { tableNameStocks, tableNameOrders, tableNameProfiles } from '../lib/const'; +import { tableNameStocks, tableNameOrders } from '../lib/const'; import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; +import { fetchTradeProfiles } from '../lib/profileApi'; interface PositionsTabProps { botState: BotState; @@ -523,16 +524,17 @@ export const PositionsTab = ({ botState }: PositionsTabProps) => { console.warn('[PositionsTab] History trade lookup failed:', histError.message); } - let profilesQuery = supabase - .from(tableNameProfiles) - .select('id, name') - .order('name', { ascending: true }); - - if (profile?.role !== 'admin') { - profilesQuery = profilesQuery.eq('user_id', user.id); - } - - const { data: profData, error: profError } = await profilesQuery; + let profData: Array<{ id: string; name: string }> = []; + let profError: Error | null = null; + try { + const profiles = await fetchTradeProfiles({ scope: profile?.role === 'admin' ? 'all' : 'user' }); + profData = profiles.map((item: any) => ({ + id: String(item.id), + name: String(item.name || item.id || 'Unnamed Profile') + })); + } catch (error) { + profError = error as Error; + } if (posError) { console.error('[PositionsTab] Failed loading manual positions:', posError.message);