Split frontend data services by domain
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m33s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m33s
This commit is contained in:
48
src/services/attendancesService.ts
Normal file
48
src/services/attendancesService.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Attendance, DashboardFilter } from '../types';
|
||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getAttendances = async (tenantId: string, filter: DashboardFilter): Promise<Attendance[]> => {
|
||||||
|
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 apiFetch(`${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);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAttendanceById = async (id: string): Promise<Attendance | undefined> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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;
|
||||||
|
}
|
||||||
|
};
|
||||||
180
src/services/authService.ts
Normal file
180
src/services/authService.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import { API_URL, apiFetch, getHeaders, setSessionExpiredHandler } from './apiClient';
|
||||||
|
|
||||||
|
export let isReloadingForImpersonation = false;
|
||||||
|
|
||||||
|
export const logout = () => {
|
||||||
|
if (isReloadingForImpersonation) return;
|
||||||
|
|
||||||
|
const refreshToken = localStorage.getItem('ctms_refresh_token');
|
||||||
|
|
||||||
|
localStorage.removeItem('ctms_token');
|
||||||
|
localStorage.removeItem('ctms_refresh_token');
|
||||||
|
localStorage.removeItem('ctms_user_id');
|
||||||
|
localStorage.removeItem('ctms_tenant_id');
|
||||||
|
|
||||||
|
if (refreshToken) {
|
||||||
|
fetch(`${API_URL}/auth/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ refreshToken })
|
||||||
|
}).catch(e => console.error("Failed to revoke refresh token", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setSessionExpiredHandler(logout);
|
||||||
|
|
||||||
|
export const login = async (credentials: any): Promise<any> => {
|
||||||
|
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);
|
||||||
|
if (data.refreshToken) localStorage.setItem('ctms_refresh_token', data.refreshToken);
|
||||||
|
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<any> => {
|
||||||
|
const response = await apiFetch(`${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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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<boolean> => {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
const response = await apiFetch(`${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<string> => {
|
||||||
|
const response = await apiFetch(`${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<string> => {
|
||||||
|
const response = await apiFetch(`${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;
|
||||||
|
};
|
||||||
@@ -1,771 +1,10 @@
|
|||||||
|
export * from './apiClient';
|
||||||
import { Attendance, DashboardFilter, User } from '../types';
|
export * from './attendancesService';
|
||||||
import { API_URL, apiFetch, getHeaders, setSessionExpiredHandler } from './apiClient';
|
export * from './authService';
|
||||||
|
export * from './funnelsService';
|
||||||
export const getNotifications = async (): Promise<any[]> => {
|
export * from './integrationsService';
|
||||||
try {
|
export * from './notificationsService';
|
||||||
const response = await apiFetch(`${API_URL}/notifications`, {
|
export * from './originsService';
|
||||||
headers: getHeaders()
|
export * from './teamsService';
|
||||||
});
|
export * from './tenantsService';
|
||||||
if (!response.ok) throw new Error('Failed to fetch notifications');
|
export * from './usersService';
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("API Error (getNotifications):", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const markNotificationAsRead = async (id: string): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any[]> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any> => {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any> => {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any[]> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any> => {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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, color_class?: string }): Promise<any> => {
|
|
||||||
const response = await apiFetch(`${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, color_class?: string }): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any[]> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any> => {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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 apiFetch(`${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<Attendance[]> => {
|
|
||||||
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 apiFetch(`${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<User[]> => {
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
if (tenantId !== 'all') params.append('tenantId', tenantId);
|
|
||||||
|
|
||||||
const response = await apiFetch(`${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<User | undefined> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<string | null> => {
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('avatar', file);
|
|
||||||
|
|
||||||
const token = localStorage.getItem('ctms_token');
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<Attendance | undefined> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any[]> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<any[]> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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 apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await apiFetch(`${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
|
|
||||||
|
|
||||||
const refreshToken = localStorage.getItem('ctms_refresh_token');
|
|
||||||
|
|
||||||
// Clear local storage synchronously for instant UI update
|
|
||||||
localStorage.removeItem('ctms_token');
|
|
||||||
localStorage.removeItem('ctms_refresh_token');
|
|
||||||
localStorage.removeItem('ctms_user_id');
|
|
||||||
localStorage.removeItem('ctms_tenant_id');
|
|
||||||
|
|
||||||
// Attempt to revoke in background
|
|
||||||
if (refreshToken) {
|
|
||||||
fetch(`${API_URL}/auth/logout`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ refreshToken })
|
|
||||||
}).catch(e => console.error("Failed to revoke refresh token", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setSessionExpiredHandler(logout);
|
|
||||||
|
|
||||||
export const login = async (credentials: any): Promise<any> => {
|
|
||||||
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);
|
|
||||||
if (data.refreshToken) localStorage.setItem('ctms_refresh_token', data.refreshToken);
|
|
||||||
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<any> => {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
const response = await apiFetch(`${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<boolean> => {
|
|
||||||
const response = await apiFetch(`${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<string> => {
|
|
||||||
const response = await apiFetch(`${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<string> => {
|
|
||||||
const response = await apiFetch(`${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;
|
|
||||||
};
|
|
||||||
|
|||||||
94
src/services/funnelsService.ts
Normal file
94
src/services/funnelsService.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getFunnels = async (tenantId: string): Promise<any[]> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<any> => {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<any> => {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/funnel_stages/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error (deleteFunnelStage):", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
54
src/services/integrationsService.ts
Normal file
54
src/services/integrationsService.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { User } from '../types';
|
||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getApiKeys = async (tenantId: string): Promise<any[]> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<any> => {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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 apiFetch(`${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: [] };
|
||||||
|
}
|
||||||
|
};
|
||||||
66
src/services/notificationsService.ts
Normal file
66
src/services/notificationsService.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getNotifications = async (): Promise<any[]> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/notifications/clear-all`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error (clearAllNotifications):", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
94
src/services/originsService.ts
Normal file
94
src/services/originsService.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getOrigins = async (tenantId: string): Promise<any[]> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<any> => {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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, color_class?: string }): Promise<any> => {
|
||||||
|
const response = await apiFetch(`${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, color_class?: string }): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/origin_items/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error (deleteOriginItem):", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
55
src/services/teamsService.ts
Normal file
55
src/services/teamsService.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getTeams = async (tenantId: string): Promise<any[]> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/teams/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error (deleteTeam):", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
62
src/services/tenantsService.ts
Normal file
62
src/services/tenantsService.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getTenants = async (): Promise<any[]> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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 createTenant = async (tenantData: any): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/tenants/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error (deleteTenant):", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
117
src/services/usersService.ts
Normal file
117
src/services/usersService.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { User } from '../types';
|
||||||
|
import { API_URL, apiFetch, getHeaders } from './apiClient';
|
||||||
|
|
||||||
|
export const getUsers = async (tenantId: string): Promise<User[]> => {
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (tenantId !== 'all') params.append('tenantId', tenantId);
|
||||||
|
|
||||||
|
const response = await apiFetch(`${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<User | undefined> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/users/${id}`, {
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 401 || response.status === 403 || response.status === 404) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateUser = async (id: string, userData: any): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<string | null> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('avatar', file);
|
||||||
|
|
||||||
|
const token = localStorage.getItem('ctms_token');
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${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<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await apiFetch(`${API_URL}/users/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: getHeaders()
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("API Error (deleteUser):", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user