feat: implement customizable funnel stages per tenant

- Modified attendances.funnel_stage in DB from ENUM to VARCHAR.

- Created tenant_funnels table and backend API routes to manage custom stages.

- Added /admin/funnels page for Admins/Managers to create, edit, order, and color-code their funnel stages.

- Updated Dashboard, UserDetail, and AttendanceDetail to fetch and render dynamic funnel stages instead of hardcoded enums.

- Added defensive checks and logging to GET /users/:idOrSlug to fix sporadic 500 errors during impersonation handoffs.
This commit is contained in:
Cauê Faleiros
2026-03-13 10:25:23 -03:00
parent 1d49161a05
commit 7ab54053db
18 changed files with 588 additions and 33 deletions

View File

@@ -82,6 +82,60 @@ export const clearAllNotifications = async (): Promise<boolean> => {
}
};
// --- Funnels Functions ---
export const getFunnels = async (tenantId: string): Promise<any[]> => {
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: any): Promise<any> => {
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: any): Promise<boolean> => {
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<boolean> => {
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 searchGlobal = async (query: string): Promise<{ members: User[], teams: any[], attendances: any[], organizations?: any[] }> => {
try {
const response = await fetch(`${API_URL}/search?q=${encodeURIComponent(query)}`, {