import { Attendance, DashboardFilter, User } from '../types'; // URL do Backend // Em produção (import.meta.env.PROD), usa caminho relativo '/api' pois o backend serve o frontend // Em desenvolvimento, aponta para o localhost:3001 const API_URL = import.meta.env.PROD ? '/api' : 'http://localhost:3001/api'; const getHeaders = () => { const token = localStorage.getItem('ctms_token'); // Evitar enviar "undefined" ou "null" como strings se o localStorage estiver corrompido if (!token || token === 'undefined' || token === 'null') return { 'Content-Type': 'application/json' }; return { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }; }; export const getNotifications = async (): Promise => { try { const response = await fetch(`${API_URL}/notifications`, { headers: getHeaders() }); if (!response.ok) throw new Error('Failed to fetch notifications'); return await response.json(); } catch (error) { console.error("API Error (getNotifications):", error); return []; } }; export const markNotificationAsRead = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/notifications/${id}`, { method: 'PUT', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (markNotificationAsRead):", error); return false; } }; export const markAllNotificationsAsRead = async (): Promise => { try { const response = await fetch(`${API_URL}/notifications/read-all`, { method: 'PUT', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (markAllNotificationsAsRead):", error); return false; } }; export const deleteNotification = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/notifications/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteNotification):", error); return false; } }; export const clearAllNotifications = async (): Promise => { try { const response = await fetch(`${API_URL}/notifications/clear-all`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (clearAllNotifications):", error); return false; } }; // --- Funnels Functions --- export const getFunnels = async (tenantId: string): Promise => { try { const response = await fetch(`${API_URL}/funnels?tenantId=${tenantId}`, { headers: getHeaders() }); if (!response.ok) throw new Error('Falha ao buscar funis'); return await response.json(); } catch (error) { console.error("API Error (getFunnels):", error); return []; } }; export const createFunnel = async (data: { name: string, tenantId: string }): Promise => { const response = await fetch(`${API_URL}/funnels`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Erro ao criar funil'); } return await response.json(); }; export const updateFunnel = async (id: string, data: { name?: string, teamIds?: string[] }): Promise => { try { const response = await fetch(`${API_URL}/funnels/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(data) }); return response.ok; } catch (error) { console.error("API Error (updateFunnel):", error); return false; } }; export const deleteFunnel = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/funnels/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteFunnel):", error); return false; } }; export const createFunnelStage = async (funnelId: string, data: any): Promise => { const response = await fetch(`${API_URL}/funnels/${funnelId}/stages`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Erro ao criar etapa'); } return await response.json(); }; export const updateFunnelStage = async (id: string, data: any): Promise => { try { const response = await fetch(`${API_URL}/funnel_stages/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(data) }); return response.ok; } catch (error) { console.error("API Error (updateFunnelStage):", error); return false; } }; export const deleteFunnelStage = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/funnel_stages/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteFunnelStage):", error); return false; } }; // --- Origins Functions --- export const getOrigins = async (tenantId: string): Promise => { try { const response = await fetch(`${API_URL}/origins?tenantId=${tenantId}`, { headers: getHeaders() }); if (!response.ok) throw new Error('Falha ao buscar origens'); return await response.json(); } catch (error) { console.error("API Error (getOrigins):", error); return []; } }; export const createOriginGroup = async (data: { name: string, tenantId: string }): Promise => { const response = await fetch(`${API_URL}/origins`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Erro ao criar grupo de origens'); } return await response.json(); }; export const updateOriginGroup = async (id: string, data: { name?: string, teamIds?: string[] }): Promise => { try { const response = await fetch(`${API_URL}/origins/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(data) }); return response.ok; } catch (error) { console.error("API Error (updateOriginGroup):", error); return false; } }; export const deleteOriginGroup = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/origins/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteOriginGroup):", error); return false; } }; export const createOriginItem = async (groupId: string, data: { name: string }): Promise => { const response = await fetch(`${API_URL}/origins/${groupId}/items`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Erro ao criar item de origem'); } return await response.json(); }; export const updateOriginItem = async (id: string, data: { name: string }): Promise => { try { const response = await fetch(`${API_URL}/origin_items/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(data) }); return response.ok; } catch (error) { console.error("API Error (updateOriginItem):", error); return false; } }; export const deleteOriginItem = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/origin_items/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteOriginItem):", error); return false; } }; // --- API Keys Functions --- export const getApiKeys = async (tenantId: string): Promise => { try { const response = await fetch(`${API_URL}/api-keys?tenantId=${tenantId}`, { headers: getHeaders() }); if (!response.ok) throw new Error('Falha ao buscar chaves'); return await response.json(); } catch (error) { console.error("API Error (getApiKeys):", error); return []; } }; export const createApiKey = async (data: { name: string, tenantId: string }): Promise => { const response = await fetch(`${API_URL}/api-keys`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Erro ao criar chave de API'); } return await response.json(); }; export const deleteApiKey = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/api-keys/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteApiKey):", error); return false; } }; export const searchGlobal = async (query: string): Promise<{ members: User[], teams: any[], attendances: any[], organizations?: any[] }> => { try { const response = await fetch(`${API_URL}/search?q=${encodeURIComponent(query)}`, { headers: getHeaders() }); if (!response.ok) throw new Error('Search failed'); return await response.json(); } catch (error) { console.error("API Error (searchGlobal):", error); return { members: [], teams: [], attendances: [], organizations: [] }; } }; export const getAttendances = async (tenantId: string, filter: DashboardFilter): Promise => { try { const params = new URLSearchParams(); params.append('tenantId', tenantId); if (filter.dateRange.start) params.append('startDate', filter.dateRange.start.toISOString()); if (filter.dateRange.end) params.append('endDate', filter.dateRange.end.toISOString()); if (filter.userId && filter.userId !== 'all') params.append('userId', filter.userId); if (filter.teamId && filter.teamId !== 'all') params.append('teamId', filter.teamId); if (filter.funnelStage && filter.funnelStage !== 'all') params.append('funnelStage', filter.funnelStage); if (filter.origin && filter.origin !== 'all') params.append('origin', filter.origin); const response = await fetch(`${API_URL}/attendances?${params.toString()}`, { headers: getHeaders() }); if (!response.ok) { throw new Error('Falha ao buscar atendimentos do servidor'); } return await response.json(); } catch (error) { console.error("API Error (getAttendances):", error); // Fallback vazio ou lançar erro para a UI tratar return []; } }; export const getUsers = async (tenantId: string): Promise => { try { const params = new URLSearchParams(); if (tenantId !== 'all') params.append('tenantId', tenantId); const response = await fetch(`${API_URL}/users?${params.toString()}`, { headers: getHeaders() }); if (!response.ok) throw new Error('Falha ao buscar usuários'); return await response.json(); } catch (error) { console.error("API Error (getUsers):", error); return []; } }; export const getUserById = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/users/${id}`, { headers: getHeaders() }); if (!response.ok) { if (response.status === 401 || response.status === 403 || response.status === 404) { return undefined; // Invalid user or token } throw new Error(`Server error: ${response.status}`); } const contentType = response.headers.get("content-type"); if (contentType && contentType.indexOf("application/json") !== -1) { return await response.json(); } } catch (error) { console.error("API Error (getUserById):", error); throw error; // Rethrow so AuthGuard catches it and doesn't wipe tokens } }; export const updateUser = async (id: string, userData: any): Promise => { try { const response = await fetch(`${API_URL}/users/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(userData) }); if (!response.ok) { const errorData = await response.json().catch(() => null); throw new Error(errorData?.error || 'Erro ao atualizar usuário no servidor'); } return true; } catch (error) { console.error("API Error (updateUser):", error); throw error; } }; export const uploadAvatar = async (id: string, file: File): Promise => { try { const formData = new FormData(); formData.append('avatar', file); const token = localStorage.getItem('ctms_token'); const response = await fetch(`${API_URL}/users/${id}/avatar`, { method: 'POST', headers: { ...(token ? { 'Authorization': `Bearer ${token}` } : {}) }, body: formData }); if (!response.ok) throw new Error('Falha no upload'); const data = await response.json(); return data.avatarUrl; } catch (error) { console.error("API Error (uploadAvatar):", error); return null; } }; export const createMember = async (userData: any): Promise => { try { const response = await fetch(`${API_URL}/users`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(userData) }); if (!response.ok) { const errorData = await response.json().catch(() => null); throw new Error(errorData?.error || 'Erro ao criar membro no servidor'); } return true; } catch (error) { console.error("API Error (createMember):", error); throw error; } }; export const deleteUser = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/users/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteUser):", error); return false; } }; export const getAttendanceById = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/attendances/${id}`, { headers: getHeaders() }); if (!response.ok) return undefined; const contentType = response.headers.get("content-type"); if (contentType && contentType.indexOf("application/json") !== -1) { return await response.json(); } return undefined; } catch (error) { console.error("API Error (getAttendanceById):", error); return undefined; } }; export const getTenants = async (): Promise => { try { const response = await fetch(`${API_URL}/tenants`, { headers: getHeaders() }); if (!response.ok) throw new Error('Falha ao buscar tenants'); const contentType = response.headers.get("content-type"); if (contentType && contentType.indexOf("application/json") !== -1) { return await response.json(); } return []; } catch (error) { console.error("API Error (getTenants):", error); return []; } }; export const getTeams = async (tenantId: string): Promise => { try { const response = await fetch(`${API_URL}/teams?tenantId=${tenantId}`, { headers: getHeaders() }); if (!response.ok) throw new Error('Falha ao buscar equipes'); return await response.json(); } catch (error) { console.error("API Error (getTeams):", error); return []; } }; export const createTeam = async (teamData: any): Promise => { try { const response = await fetch(`${API_URL}/teams`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(teamData) }); return response.ok; } catch (error) { console.error("API Error (createTeam):", error); return false; } }; export const updateTeam = async (id: string, teamData: any): Promise => { try { const response = await fetch(`${API_URL}/teams/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(teamData) }); return response.ok; } catch (error) { console.error("API Error (updateTeam):", error); return false; } }; export const deleteTeam = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/teams/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteTeam):", error); return false; } }; export const createTenant = async (tenantData: any): Promise<{ success: boolean; message?: string }> => { try { const response = await fetch(`${API_URL}/tenants`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(tenantData) }); const data = await response.json().catch(() => null); if (!response.ok) throw new Error(data?.error || 'Erro ao criar organização'); return { success: true, message: data?.message || 'Organização criada!' }; } catch (error: any) { console.error("API Error (createTenant):", error); return { success: false, message: error.message }; } }; export const updateTenant = async (id: string, tenantData: any): Promise => { try { const response = await fetch(`${API_URL}/tenants/${id}`, { method: 'PUT', headers: getHeaders(), body: JSON.stringify(tenantData) }); return response.ok; } catch (error) { console.error("API Error (updateTenant):", error); return false; } }; export const deleteTenant = async (id: string): Promise => { try { const response = await fetch(`${API_URL}/tenants/${id}`, { method: 'DELETE', headers: getHeaders() }); return response.ok; } catch (error) { console.error("API Error (deleteTenant):", error); return false; } }; // --- Auth Functions --- // Flag to prevent background fetches from throwing 401 and logging out during impersonation handoffs export let isReloadingForImpersonation = false; export const logout = () => { if (isReloadingForImpersonation) return; // Prevent logout if we are just switching tokens localStorage.removeItem('ctms_token'); localStorage.removeItem('ctms_user_id'); localStorage.removeItem('ctms_tenant_id'); }; export const login = async (credentials: any): Promise => { const response = await fetch(`${API_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); const contentType = response.headers.get("content-type"); const isJson = contentType && contentType.indexOf("application/json") !== -1; if (!response.ok) { const error = isJson ? await response.json() : { error: 'Erro no servidor' }; throw new Error(error.error || 'Erro no login'); } const data = isJson ? await response.json() : null; if (data && data.token) { localStorage.setItem('ctms_token', data.token); localStorage.setItem('ctms_user_id', data.user.id); localStorage.setItem('ctms_tenant_id', data.user.tenant_id || ''); } return data; }; export const impersonateTenant = async (tenantId: string): Promise => { const response = await fetch(`${API_URL}/impersonate/${tenantId}`, { method: 'POST', headers: getHeaders() }); const contentType = response.headers.get("content-type"); const isJson = contentType && contentType.indexOf("application/json") !== -1; if (!response.ok) { const errorData = isJson ? await response.json() : { error: 'Erro no servidor' }; throw new Error(errorData.error || 'Erro ao assumir identidade'); } isReloadingForImpersonation = true; // Block logouts const data = await response.json(); const oldToken = localStorage.getItem('ctms_token'); if (oldToken) { localStorage.setItem('ctms_super_admin_token', oldToken); } localStorage.setItem('ctms_token', data.token); localStorage.setItem('ctms_user_id', data.user.id); localStorage.setItem('ctms_tenant_id', data.user.tenant_id || ''); window.location.hash = '#/'; window.location.reload(); return data; }; export const returnToSuperAdmin = (): boolean => { const superAdminToken = localStorage.getItem('ctms_super_admin_token'); if (superAdminToken) { try { isReloadingForImpersonation = true; // Block logouts // Correctly decode Base64Url JWT payload with proper padding const base64Url = superAdminToken.split('.')[1]; let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const pad = base64.length % 4; if (pad) { base64 += '='.repeat(4 - pad); } const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); const payload = JSON.parse(jsonPayload); localStorage.setItem('ctms_token', superAdminToken); localStorage.setItem('ctms_user_id', payload.id); localStorage.setItem('ctms_tenant_id', payload.tenant_id || 'system'); localStorage.removeItem('ctms_super_admin_token'); window.location.hash = '#/super-admin'; window.location.reload(); return true; } catch (e) { isReloadingForImpersonation = false; console.error("Failed to restore super admin token", e); return false; } } return false; }; export const register = async (userData: any): Promise => { const response = await fetch(`${API_URL}/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Erro no registro'); } return true; }; export const verifyCode = async (data: any): Promise => { const response = await fetch(`${API_URL}/auth/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Código inválido ou expirado'); } return true; }; export const forgotPassword = async (email: string): Promise => { const response = await fetch(`${API_URL}/auth/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); const contentType = response.headers.get("content-type"); const isJson = contentType && contentType.indexOf("application/json") !== -1; if (!response.ok) { const error = isJson ? await response.json() : { error: 'Erro no servidor' }; throw new Error(error.error || 'Erro ao processar solicitação'); } const data = isJson ? await response.json() : { message: 'Solicitação processada' }; return data.message; }; export const resetPassword = async (password: string, token: string, name?: string): Promise => { const response = await fetch(`${API_URL}/auth/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password, token, name }) }); const contentType = response.headers.get("content-type"); const isJson = contentType && contentType.indexOf("application/json") !== -1; if (!response.ok) { const error = isJson ? await response.json() : { error: 'Erro no servidor' }; throw new Error(error.error || 'Erro ao resetar senha'); } const data = isJson ? await response.json() : { message: 'Senha redefinida' }; return data.message; };