setMobileOpen(false)}
+ />
+ )}
+
+ {/* Mobile sidebar */}
+
+
+ {/* Desktop sidebar */}
+
+ >
+ );
+}
diff --git a/src/modules/intelligence-ia/components/layout/MainLayout.tsx b/src/modules/intelligence-ia/components/layout/MainLayout.tsx
new file mode 100644
index 0000000..7444873
--- /dev/null
+++ b/src/modules/intelligence-ia/components/layout/MainLayout.tsx
@@ -0,0 +1,19 @@
+import { ReactNode } from "react";
+import { AppSidebar } from "./AppSidebar";
+
+interface MainLayoutProps {
+ children: ReactNode;
+}
+
+export function MainLayout({ children }: MainLayoutProps) {
+ return (
+
+ );
+}
diff --git a/src/modules/intelligence-ia/pages/Dashboard.tsx b/src/modules/intelligence-ia/pages/Dashboard.tsx
new file mode 100644
index 0000000..d0285ea
--- /dev/null
+++ b/src/modules/intelligence-ia/pages/Dashboard.tsx
@@ -0,0 +1,112 @@
+import { useEffect } from "react";
+import { format } from "date-fns";
+import { ptBR } from "date-fns/locale";
+import { Bot, Sparkles, Zap, Brain, MessageCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { GlobalFunctions } from "@/GlobalFunctions";
+export default function Dashboard() {
+ const today = new Date();
+ const formattedDate = format(today, "EEEE, d 'de' MMMM 'de' yyyy", { locale: ptBR });
+ const hour = today.getHours();
+
+ const getGreeting = () => {
+ if (hour < 12) return "Bom dia";
+ if (hour < 18) return "Boa tarde";
+ return "Boa noite";
+ };
+
+
+
+
+ return (
+
+ {/* Floating particles background effect */}
+
+
+ {/* AI Agent Avatar */}
+
+ {/* Outer glow ring */}
+
+
+ {/* Middle ring */}
+
+
+ {/* Inner gradient effect */}
+
+
+ {/* Bot icon */}
+
+
+ {/* Scanning line effect */}
+
+
+
+
+ {/* Orbiting icons */}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Greeting */}
+
+
+ {getGreeting()}! 👋
+
+
+ Seu agente de IA está pronto para ajudar
+
+
+
+ {/* Date card */}
+
+
+ 📅 {formattedDate}
+
+
+
+ {/* Status indicator */}
+
+
+
+
+
+ Sistema operacional
+
+
+ {/* WhatsApp Button */}
+
+
+ );
+}
+
+
+
diff --git a/src/modules/intelligence-ia/pages/Financas.tsx b/src/modules/intelligence-ia/pages/Financas.tsx
new file mode 100644
index 0000000..8a521cd
--- /dev/null
+++ b/src/modules/intelligence-ia/pages/Financas.tsx
@@ -0,0 +1,778 @@
+import { useState, useMemo, useEffect, useCallback } from "react";
+import { format } from "date-fns";
+import { ptBR } from "date-fns/locale";
+import {
+ DollarSign,
+ TrendingUp,
+ TrendingDown,
+ Building2,
+ User,
+ Receipt,
+ Filter,
+ Calendar,
+ Search,
+ CalendarIcon,
+ Loader2,
+} from "lucide-react";
+import { cn } from "@/lib/utils";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Calendar as CalendarComponent } from "@/components/ui/calendar";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import { Badge } from "@/components/ui/badge";
+import { userProfileService, FinancialIndicators, ExpenseItem, ExpensesResponse, ExpensesFilters, ExpenseCategory } from "@/services/userProfile";
+import { GlobalFunctions, TransferAreaProperties } from "@/GlobalFunctions";
+import { toast } from "@/hooks/use-toast";
+
+// Mapeamento de categoria_id para cores (cores padrão caso não tenha categoria)
+const categoryColorMap: Record
= {
+ 1: "bg-orange-500/10 text-orange-600 border-orange-500/20", // Alimentação
+ 2: "bg-yellow-500/10 text-yellow-600 border-yellow-500/20", // Combustível
+ 3: "bg-purple-500/10 text-purple-600 border-purple-500/20", // Hospedagem
+ 4: "bg-blue-500/10 text-blue-600 border-blue-500/20", // Estacionamento
+ 5: "bg-indigo-500/10 text-indigo-600 border-indigo-500/20", // Pedágio
+ 6: "bg-cyan-500/10 text-cyan-600 border-cyan-500/20", // Transporte
+};
+
+// Função auxiliar para obter informações da categoria
+const getCategoryInfo = (categoriaId: number | undefined | null, categories: ExpenseCategory[]) => {
+ // Se não tem categoria_id válido, retorna padrão
+ if (categoriaId === undefined || categoriaId === null || categoriaId === 0) {
+ return {
+ label: "Outros",
+ color: "bg-gray-500/10 text-gray-600 border-gray-500/20",
+ };
+ }
+
+ // Converte para número caso venha como string
+ const id = typeof categoriaId === 'string' ? parseInt(categoriaId, 10) : categoriaId;
+
+ // Busca a categoria na lista carregada
+ const category = categories.find(cat => cat.id === id);
+
+ // Se encontrou a categoria, usa o nome dela
+ if (category) {
+ const color = categoryColorMap[id] || "bg-gray-500/10 text-gray-600 border-gray-500/20";
+ return {
+ label: category.nome,
+ color: color,
+ };
+ }
+
+ // Se não encontrou mas tem ID válido, usa o ID com cor padrão
+ const color = categoryColorMap[id] || "bg-gray-500/10 text-gray-600 border-gray-500/20";
+ return {
+ label: `Categoria ${id}`,
+ color: color,
+ };
+};
+
+function formatCurrency(value: number) {
+ return new Intl.NumberFormat("pt-BR", {
+ style: "currency",
+ currency: "BRL",
+ }).format(value);
+}
+
+function formatDateTime(dateTimeString: string) {
+ // Formata "2026-01-14 10:38:57" para Date
+ const date = new Date(dateTimeString.replace(" ", "T"));
+ return {
+ date: date.toLocaleDateString("pt-BR"),
+ time: date.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" }),
+ };
+}
+
+export default function Financas() {
+ const [searchTerm, setSearchTerm] = useState("");
+ const [typeFilter, setTypeFilter] = useState("all");
+ const [categoryFilter, setCategoryFilter] = useState("all"); // Agora será o ID da categoria ou "all"
+ const [startDate, setStartDate] = useState(undefined);
+ const [endDate, setEndDate] = useState(undefined);
+ const [currentPage, setCurrentPage] = useState(1);
+ const itemsPerPage = 10;
+
+ // Estados para os indicadores financeiros da API
+ const [loadingIndicators, setLoadingIndicators] = useState(true);
+ const [indicators, setIndicators] = useState(null);
+
+ // Estados para as despesas da API
+ const [loadingExpenses, setLoadingExpenses] = useState(true);
+ const [expensesData, setExpensesData] = useState(null);
+ const [userEmail, setUserEmail] = useState("");
+
+ // Estados para as categorias da API
+ const [loadingCategories, setLoadingCategories] = useState(true);
+ const [categories, setCategories] = useState([]);
+
+ // Carrega os indicadores financeiros da API
+ useEffect(() => {
+ const loadFinancialIndicators = async () => {
+ try {
+ setLoadingIndicators(true);
+
+ // Obtém o email do usuário logado
+ let userEmail = GlobalFunctions.getUsuarioLogado().email;
+
+ // Se não tem email, tenta obter do Transfer Area como fallback
+ if (!userEmail) {
+ const transferEmail = GlobalFunctions.getTransferProperty(
+ TransferAreaProperties.UsuarioEmail
+ );
+ if (transferEmail) {
+ userEmail = transferEmail as string;
+ }
+ }
+
+ // Se ainda não tem email, tenta obter do token novamente após refresh
+ if (!userEmail) {
+ try {
+ await GlobalFunctions.getToken();
+ await new Promise(resolve => setTimeout(resolve, 500));
+ userEmail = GlobalFunctions.getUsuarioLogado().email;
+ } catch (error) {
+ console.error("Erro ao fazer refresh do token:", error);
+ }
+ }
+
+ if (!userEmail) {
+ console.error("Financas: Não foi possível obter email do usuário");
+ toast({
+ title: "Erro",
+ description: "Email do usuário não encontrado. Por favor, faça login novamente.",
+ variant: "destructive",
+ });
+ setLoadingIndicators(false);
+ return;
+ }
+
+ // Busca os indicadores financeiros da API
+ const response = await userProfileService.getFinancialIndicators(userEmail);
+
+ if (response.success) {
+ setIndicators(response);
+ } else {
+ throw new Error(response.message || "Erro ao carregar indicadores financeiros");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao carregar indicadores financeiros:", error);
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ toast({
+ title: "Erro",
+ description: errorMessage || "Erro ao carregar indicadores financeiros.",
+ variant: "destructive",
+ });
+ } finally {
+ setLoadingIndicators(false);
+ }
+ };
+
+ loadFinancialIndicators();
+ }, []);
+
+ // Função auxiliar para obter o email do usuário
+ const getUserEmail = async (): Promise => {
+ let email = GlobalFunctions.getUsuarioLogado().email;
+
+ if (!email) {
+ const transferEmail = GlobalFunctions.getTransferProperty(
+ TransferAreaProperties.UsuarioEmail
+ );
+ if (transferEmail) {
+ email = transferEmail as string;
+ }
+ }
+
+ if (!email) {
+ try {
+ await GlobalFunctions.getToken();
+ await new Promise(resolve => setTimeout(resolve, 500));
+ email = GlobalFunctions.getUsuarioLogado().email;
+ } catch (error) {
+ console.error("Erro ao fazer refresh do token:", error);
+ }
+ }
+
+ return email || null;
+ };
+
+ // Carrega as despesas da API
+ const loadExpenses = useCallback(async (page: number = 1) => {
+ const emailToUse = userEmail || await getUserEmail();
+ if (!emailToUse) {
+ setLoadingExpenses(false);
+ return;
+ }
+
+ if (!userEmail && emailToUse) {
+ setUserEmail(emailToUse);
+ }
+
+ try {
+ setLoadingExpenses(true);
+
+ // Prepara filtros para a API
+ const filters: ExpensesFilters = {
+ page,
+ per_page: itemsPerPage,
+ };
+
+ if (searchTerm.trim()) {
+ filters.descricao = searchTerm.trim();
+ }
+
+ if (categoryFilter !== "all") {
+ // Usa o ID da categoria diretamente
+ const categoriaId = parseInt(categoryFilter, 10);
+ if (!isNaN(categoriaId)) {
+ filters.categoria_id = categoriaId;
+ }
+ }
+
+ if (startDate) {
+ filters.data_inicial = format(startDate, "yyyy-MM-dd");
+ }
+
+ if (endDate) {
+ filters.data_final = format(endDate, "yyyy-MM-dd");
+ }
+
+ const response = await userProfileService.getExpenses(emailToUse, filters);
+
+ if (response.success) {
+ // Garante que a resposta tenha estrutura válida mesmo quando não há dados
+ const safeResponse: ExpensesResponse = {
+ success: response.success,
+ total_registros: response.total_registros || 0,
+ total_paginas: response.total_paginas || 0,
+ per_page: response.per_page || itemsPerPage,
+ pagina_atual: response.pagina_atual || page,
+ data: Array.isArray(response.data)
+ ? response.data.filter((item) => item && item.id && Object.keys(item).length > 0)
+ : [],
+ };
+ setExpensesData(safeResponse);
+ } else {
+ throw new Error("Erro ao carregar despesas");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao carregar despesas:", error);
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ toast({
+ title: "Erro",
+ description: errorMessage || "Erro ao carregar despesas.",
+ variant: "destructive",
+ });
+ } finally {
+ setLoadingExpenses(false);
+ }
+ }, [userEmail, searchTerm, categoryFilter, startDate, endDate, itemsPerPage]);
+
+ // Carrega o email do usuário e as categorias na montagem do componente
+ useEffect(() => {
+ const initData = async () => {
+ // Carrega email do usuário
+ const email = await getUserEmail();
+ if (email) {
+ setUserEmail(email);
+ }
+
+ // Carrega categorias
+ try {
+ setLoadingCategories(true);
+ const categoriesData = await userProfileService.getCategories();
+ console.log("Categorias carregadas:", categoriesData);
+ setCategories(categoriesData);
+ } catch (error) {
+ console.error("Erro ao carregar categorias:", error);
+ toast({
+ title: "Aviso",
+ description: "Não foi possível carregar as categorias. Usando categorias padrão.",
+ variant: "default",
+ });
+ } finally {
+ setLoadingCategories(false);
+ }
+ };
+ initData();
+ }, []);
+
+ // Debounce para busca de descrição
+ useEffect(() => {
+ const timeoutId = setTimeout(() => {
+ if (userEmail) {
+ setCurrentPage(1); // Reseta para página 1 ao buscar
+ loadExpenses(1);
+ }
+ }, 500); // Aguarda 500ms após parar de digitar
+
+ return () => clearTimeout(timeoutId);
+ }, [searchTerm, userEmail, loadExpenses]);
+
+ // Carrega despesas quando os filtros ou página mudam (exceto searchTerm que tem debounce)
+ useEffect(() => {
+ if (userEmail) {
+ loadExpenses(currentPage);
+ }
+ }, [currentPage, typeFilter, categoryFilter, startDate, endDate, userEmail, loadExpenses]);
+
+ // Calculate totals - usa dados da API se disponível
+ const totals = useMemo(() => {
+ if (indicators && indicators.success) {
+ return {
+ total: parseFloat(indicators.total_despesas) || 0,
+ corporate: parseFloat(indicators.total_corporativo) || 0,
+ personal: parseFloat(indicators.total_pessoal) || 0,
+ };
+ }
+
+ return { total: 0, personal: 0, corporate: 0 };
+ }, [indicators]);
+
+ // Filtra despesas por tipo (se necessário, já que a API pode não filtrar por tipo)
+ const filteredExpenses = useMemo(() => {
+ if (!expensesData || !expensesData.data || !Array.isArray(expensesData.data)) {
+ return [];
+ }
+
+ // Filtra objetos vazios ou inválidos
+ let expenses = expensesData.data.filter((expense) => {
+ // Verifica se o expense é válido e tem propriedades necessárias
+ // categoria_id pode ser opcional, então não validamos ele aqui
+ return expense &&
+ typeof expense === 'object' &&
+ expense.id &&
+ expense.descricao &&
+ expense.data_hora &&
+ expense.valor;
+ });
+
+ // Filtra por tipo se necessário (a API pode não ter esse filtro)
+ if (typeFilter !== "all") {
+ expenses = expenses.filter((expense) => expense.tipo === typeFilter);
+ }
+
+ return expenses;
+ }, [expensesData, typeFilter]);
+
+ const clearFilters = () => {
+ setSearchTerm("");
+ setTypeFilter("all");
+ setCategoryFilter("all");
+ setStartDate(undefined);
+ setEndDate(undefined);
+ setCurrentPage(1);
+ };
+
+ return (
+
+ {/* Header */}
+
+
Finanças
+
+ Análise de despesas pessoais e corporativas identificadas pelo agente
+
+
+
+ {/* Indicators */}
+
+
+
+
+
+
Total de Despesas
+ {loadingIndicators ? (
+
+
+ Carregando...
+
+ ) : (
+ <>
+
{formatCurrency(totals.total)}
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
Despesas Corporativas
+ {loadingIndicators ? (
+
+
+ Carregando...
+
+ ) : (
+ <>
+
{formatCurrency(totals.corporate)}
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
Despesas Pessoais
+ {loadingIndicators ? (
+
+
+ Carregando...
+
+ ) : (
+ <>
+
{formatCurrency(totals.personal)}
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ {/* Filters */}
+
+
+
+
+ Filtros
+
+
+
+
+ {/* First row: Search + Date filters */}
+
+
+
+
+ {
+ setSearchTerm(e.target.value);
+ setCurrentPage(1);
+ }}
+ className="pl-9"
+ />
+
+
+
+
+
+
+
+ {
+ setStartDate(date);
+ setCurrentPage(1);
+ }}
+ locale={ptBR}
+ initialFocus
+ className={cn("p-3 pointer-events-auto")}
+ />
+
+
+
+
+
+
+
+ {
+ setEndDate(date);
+ setCurrentPage(1);
+ }}
+ locale={ptBR}
+ disabled={(date) => startDate ? date < startDate : false}
+ initialFocus
+ className={cn("p-3 pointer-events-auto")}
+ />
+
+
+
+
+ {/* Second row: Type + Category + Clear */}
+
+
+
+
+
+
+
+
+
+ {/* Table */}
+
+
+
+
+ Registro de Despesas
+
+
+
+
+
+
+
+
+
+ Data/Hora
+
+
+ Descrição
+ Tipo
+ Categoria
+ Valor
+
+
+
+ {loadingExpenses ? (
+
+
+
+
+ Carregando despesas...
+
+
+
+ ) : filteredExpenses.length === 0 ? (
+
+
+ {expensesData && expensesData.total_registros === 0
+ ? "Nenhuma despesa encontrada."
+ : "Nenhuma despesa encontrada com os filtros aplicados."}
+
+
+ ) : (
+ filteredExpenses.map((expense) => {
+ // Validação adicional para garantir que o expense é válido
+ if (!expense || !expense.id || !expense.data_hora || !expense.descricao || !expense.valor) {
+ return null;
+ }
+
+ try {
+ const { date, time } = formatDateTime(expense.data_hora);
+ // Usa categoria_id diretamente (pode ser number, undefined ou null)
+ const categoriaId = expense.categoria_id;
+ const categoryInfo = getCategoryInfo(categoriaId, categories);
+ const valor = parseFloat(expense.valor) || 0;
+
+ return (
+
+
+
+ {date}
+ {time}
+
+
+
+ {expense.descricao || "Sem descrição"}
+
+
+
+ {expense.tipo === "corporativo" ? (
+
+ ) : (
+
+ )}
+ {expense.tipo === "corporativo" ? "Corp." : "Pessoal"}
+
+
+
+ {categoryInfo ? (
+
+ {categoryInfo.label}
+
+ ) : (
+
+ Sem categoria
+
+ )}
+
+
+ {formatCurrency(valor)}
+
+
+ );
+ } catch (error) {
+ console.error("Erro ao renderizar despesa:", error, expense);
+ return null;
+ }
+ }).filter(Boolean) // Remove nulls do array
+ )}
+
+
+
+ {/* Pagination */}
+ {expensesData && expensesData.total_paginas > 0 && expensesData.total_paginas > 1 && (
+
+
+ Mostrando {(currentPage - 1) * itemsPerPage + 1} a{" "}
+ {Math.min(currentPage * itemsPerPage, expensesData.total_registros)} de{" "}
+ {expensesData.total_registros} registros
+
+
+
+
+ {
+ if (currentPage > 1) {
+ setCurrentPage(currentPage - 1);
+ }
+ }}
+ className={
+ currentPage === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+ {Array.from({ length: expensesData.total_paginas }, (_, i) => i + 1).map((page) => (
+
+ setCurrentPage(page)}
+ isActive={currentPage === page}
+ className="cursor-pointer"
+ >
+ {page}
+
+
+ ))}
+
+ {
+ if (currentPage < expensesData.total_paginas) {
+ setCurrentPage(currentPage + 1);
+ }
+ }}
+ className={
+ currentPage === expensesData.total_paginas
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/modules/intelligence-ia/pages/Index.tsx b/src/modules/intelligence-ia/pages/Index.tsx
new file mode 100644
index 0000000..3b8b411
--- /dev/null
+++ b/src/modules/intelligence-ia/pages/Index.tsx
@@ -0,0 +1,7 @@
+import Dashboard from "./Dashboard";
+
+const Index = () => {
+ return ;
+};
+
+export default Index;
diff --git a/src/modules/intelligence-ia/pages/Integrations.tsx b/src/modules/intelligence-ia/pages/Integrations.tsx
new file mode 100644
index 0000000..b1d8a68
--- /dev/null
+++ b/src/modules/intelligence-ia/pages/Integrations.tsx
@@ -0,0 +1,312 @@
+import { useState, useEffect } from "react";
+import { toast } from "sonner";
+import { GoogleCalendarCard } from "@/modules/intelligence-ia/components/integrations/GoogleCalendarCard";
+import { GoogleSheetsCard } from "@/modules/intelligence-ia/components/integrations/GoogleSheetsCard";
+import { AsanaCard } from "@/modules/intelligence-ia/components/integrations/AsanaCard";
+import { asanaService } from "@/services/asana";
+import { userProfileService } from "@/services/userProfile";
+import { GlobalFunctions, TransferAreaProperties } from "@/GlobalFunctions";
+
+interface Workspace {
+ id: string;
+ name: string;
+}
+
+interface User {
+ id: string;
+ name: string;
+}
+
+
+export default function Integrations() {
+ const [asanaWorkspaces, setAsanaWorkspaces] = useState([]);
+ const [asanaUsers, setAsanaUsers] = useState([]);
+ const [loadingAsanaWorkspaces, setLoadingAsanaWorkspaces] = useState(false);
+ const [loadingAsanaUsers, setLoadingAsanaUsers] = useState(false);
+ const [asanaToken, setAsanaToken] = useState("");
+ const [asanaApiKey, setAsanaApiKey] = useState("");
+ const [selectedWorkspaceId, setSelectedWorkspaceId] = useState("");
+ const [selectedUserId, setSelectedUserId] = useState("");
+ const [loadingIntegration, setLoadingIntegration] = useState(true);
+ const [hasIntegration, setHasIntegration] = useState(false);
+ const [integrationId, setIntegrationId] = useState("");
+ const [userId, setUserId] = useState("");
+ const [saving, setSaving] = useState(false);
+
+ const handleAsanaSave = async (apiKey: string, workspaceId: string, usuarioAsanaId: string) => {
+ if (!apiKey || !workspaceId || !usuarioAsanaId) {
+ toast.error("Preencha todos os campos obrigatórios");
+ return;
+ }
+
+ // Encontra o nome do workspace e do usuário selecionado
+ const workspace = asanaWorkspaces.find(w => w.id === workspaceId);
+ const user = asanaUsers.find(u => u.id === usuarioAsanaId);
+
+ if (!workspace || !user) {
+ toast.error("Workspace ou usuário não encontrado");
+ return;
+ }
+
+ try {
+ setSaving(true);
+
+ if (hasIntegration && integrationId) {
+ // Atualiza integração existente
+ await asanaService.updateIntegration(integrationId, {
+ api_key: apiKey,
+ workspace_gid: workspaceId,
+ workspace_nome: workspace.name,
+ usuario_asana_gid: usuarioAsanaId,
+ usuario_asana_nome: user.name,
+ });
+ toast.success("Integração do Asana atualizada com sucesso!");
+ } else {
+ // Cria nova integração
+ if (!userId) {
+ toast.error("ID do usuário não encontrado");
+ return;
+ }
+
+ const response = await asanaService.createIntegration({
+ user_id: userId,
+ api_key: apiKey,
+ workspace_gid: workspaceId,
+ workspace_nome: workspace.name,
+ usuario_asana_gid: usuarioAsanaId,
+ usuario_asana_nome: user.name,
+ });
+
+ if (response.success && response.integracao_id) {
+ setIntegrationId(response.integracao_id);
+ setHasIntegration(true);
+ }
+ toast.success("Integração do Asana criada com sucesso!");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao salvar integração do Asana:", error);
+ const errorMessage = error && typeof error === 'object' && 'message' in error
+ ? (error as { message: string }).message
+ : "Erro ao salvar integração do Asana";
+ toast.error(errorMessage);
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const handleAsanaApiKeyConfirm = async (apiKey: string) => {
+ if (!apiKey || apiKey.trim().length === 0) {
+ toast.error("Insira um token válido do Asana");
+ return;
+ }
+
+ try {
+ setLoadingAsanaWorkspaces(true);
+
+ // Busca workspaces da API do Asana
+ const workspaces = await asanaService.getWorkspaces(apiKey);
+
+ if (workspaces && workspaces.length > 0) {
+ setAsanaWorkspaces(workspaces);
+ setAsanaUsers([]);
+ setAsanaToken(apiKey); // Armazena o token para usar na busca de usuários
+ toast.success(`${workspaces.length} workspace(s) encontrado(s)`);
+ } else {
+ setAsanaWorkspaces([]);
+ setAsanaUsers([]);
+ setAsanaToken("");
+ toast.warning("Nenhum workspace encontrado para este token");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao buscar workspaces do Asana:", error);
+ const errorMessage = error && typeof error === 'object' && 'message' in error
+ ? (error as { message: string }).message
+ : "Erro ao buscar workspaces do Asana. Verifique se o token está correto.";
+
+ toast.error(errorMessage);
+ setAsanaWorkspaces([]);
+ setAsanaUsers([]);
+ setAsanaToken(""); // Limpa o token em caso de erro
+ } finally {
+ setLoadingAsanaWorkspaces(false);
+ }
+ };
+
+ const handleAsanaWorkspaceChange = async (workspaceId: string) => {
+ setSelectedWorkspaceId(workspaceId);
+
+ if (!workspaceId || !asanaToken) {
+ setAsanaUsers([]);
+ setSelectedUserId("");
+ return;
+ }
+
+ try {
+ setLoadingAsanaUsers(true);
+
+ // Busca usuários do workspace da API do Asana
+ const users = await asanaService.getUsers(asanaToken, workspaceId);
+
+ if (users && users.length > 0) {
+ setAsanaUsers(users);
+ // Não mostra toast ao selecionar workspace (só quando confirmar token)
+ } else {
+ setAsanaUsers([]);
+ setSelectedUserId("");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao buscar usuários do Asana:", error);
+ const errorMessage = error && typeof error === 'object' && 'message' in error
+ ? (error as { message: string }).message
+ : "Erro ao buscar usuários do Asana. Verifique se o token está correto.";
+
+ toast.error(errorMessage);
+ setAsanaUsers([]);
+ setSelectedUserId("");
+ } finally {
+ setLoadingAsanaUsers(false);
+ }
+ };
+
+ // Carrega a integração do Asana ao montar o componente
+ useEffect(() => {
+ const loadAsanaIntegration = async () => {
+ try {
+ setLoadingIntegration(true);
+
+ // Obtém o ID do usuário do perfil
+ let userEmail = GlobalFunctions.getUsuarioLogado().email;
+
+ // Se não tem email, tenta obter do Transfer Area como fallback
+ if (!userEmail) {
+ const transferEmail = GlobalFunctions.getTransferProperty(
+ TransferAreaProperties.UsuarioEmail
+ );
+ if (transferEmail) {
+ userEmail = transferEmail as string;
+ }
+ }
+
+ // Se ainda não tem email, tenta obter do token novamente após refresh
+ if (!userEmail) {
+ try {
+ await GlobalFunctions.getToken();
+ await new Promise(resolve => setTimeout(resolve, 500));
+ userEmail = GlobalFunctions.getUsuarioLogado().email;
+ } catch (error) {
+ console.error("Erro ao fazer refresh do token:", error);
+ }
+ }
+
+ if (!userEmail) {
+ console.log("Integrations: Não foi possível obter email do usuário");
+ setLoadingIntegration(false);
+ return;
+ }
+
+ // Busca o perfil do usuário para obter o ID
+ const userProfile = await userProfileService.getUserProfile(userEmail);
+
+ if (!userProfile.success || !userProfile.id) {
+ console.log("Integrations: Usuário não encontrado ou sem ID");
+ setLoadingIntegration(false);
+ return;
+ }
+
+ // Armazena o ID do usuário para usar ao criar integração
+ setUserId(userProfile.id);
+
+ // Busca a integração do Asana
+ const integration = await asanaService.getIntegration(userProfile.id);
+
+ if (integration && integration.success) {
+ // Marca que há integração existente
+ setHasIntegration(true);
+
+ // Armazena o ID da integração (pode vir como integracao_id ou id)
+ if (integration.integracao_id) {
+ setIntegrationId(integration.integracao_id);
+ } else if (integration.id) {
+ setIntegrationId(integration.id);
+ }
+
+ // Preenche os dados da integração
+ if (integration.api_key) {
+ setAsanaApiKey(integration.api_key);
+ setAsanaToken(integration.api_key);
+
+ // Busca workspaces com o token
+ try {
+ const workspaces = await asanaService.getWorkspaces(integration.api_key);
+ setAsanaWorkspaces(workspaces);
+
+ // Se tem workspace_gid, seleciona e busca usuários
+ if (integration.workspace_gid) {
+ setSelectedWorkspaceId(integration.workspace_gid);
+
+ // Busca usuários do workspace
+ const users = await asanaService.getUsers(integration.api_key, integration.workspace_gid);
+ setAsanaUsers(users);
+
+ // Se tem usuario_asana_gid, seleciona
+ if (integration.usuario_asana_gid) {
+ setSelectedUserId(integration.usuario_asana_gid);
+ }
+ }
+ } catch (error) {
+ console.error("Erro ao carregar workspaces/usuários:", error);
+ // Continua mesmo se não conseguir carregar workspaces
+ }
+ }
+ } else {
+ // Não há integração, marca como false
+ setHasIntegration(false);
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao carregar integração do Asana:", error);
+ // Não mostra erro para o usuário, apenas deixa os campos vazios
+ } finally {
+ setLoadingIntegration(false);
+ }
+ };
+
+ loadAsanaIntegration();
+ }, []);
+
+ return (
+
+ {/* Header */}
+
+
+ Integrações
+
+
+ Configure as conexões do seu agente com serviços externos
+
+
+
+ {/* Integrations Grid */}
+
+
+ );
+}
diff --git a/src/modules/intelligence-ia/pages/MeuPerfil.tsx b/src/modules/intelligence-ia/pages/MeuPerfil.tsx
new file mode 100644
index 0000000..b4af161
--- /dev/null
+++ b/src/modules/intelligence-ia/pages/MeuPerfil.tsx
@@ -0,0 +1,516 @@
+import { useState, useEffect } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Button } from "@/components/ui/button";
+import { Switch } from "@/components/ui/switch";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { User, Mail, Phone, Save, Bell, Loader2, Plus } from "lucide-react";
+import { toast } from "@/hooks/use-toast";
+import { userProfileService, UserProfile } from "@/services/userProfile";
+import { GlobalFunctions, TransferAreaProperties } from "@/GlobalFunctions";
+
+const formatPhoneNumber = (value: string) => {
+ const numbers = value.replace(/\D/g, "");
+
+ if (numbers.length <= 2) {
+ return numbers;
+ } else if (numbers.length <= 7) {
+ return `(${numbers.slice(0, 2)}) ${numbers.slice(2)}`;
+ } else if (numbers.length <= 11) {
+ return `(${numbers.slice(0, 2)}) ${numbers.slice(2, 7)}-${numbers.slice(7)}`;
+ }
+ return `(${numbers.slice(0, 2)}) ${numbers.slice(2, 7)}-${numbers.slice(7, 11)}`;
+};
+
+const MeuPerfil = () => {
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [userExists, setUserExists] = useState(false);
+ const [showCreateModal, setShowCreateModal] = useState(false);
+ const [userId, setUserId] = useState("");
+
+ const [nomeCompleto, setNomeCompleto] = useState("");
+ const [email, setEmail] = useState("");
+ const [whatsapp, setWhatsapp] = useState("");
+ const [receberLembretes, setReceberLembretes] = useState(true);
+
+ // Estados para o modal de criação
+ const [createNome, setCreateNome] = useState("");
+ const [createWhatsapp, setCreateWhatsapp] = useState("");
+ const [createFollowup, setCreateFollowup] = useState(true);
+ const [creating, setCreating] = useState(false);
+
+ // Verifica se o usuário está logado e carrega os dados
+ useEffect(() => {
+ const checkAuthAndLoad = async () => {
+ // Verifica se há token no sessionStorage primeiro
+ const jsonUsuario = sessionStorage.getItem('usuarioLogado');
+ if (!jsonUsuario) {
+ console.log("MeuPerfil: Não há token no sessionStorage");
+ window.location.replace(import.meta.env.VITE_BASE_URL_HGTX_CORE || 'https://core.hgtx.com.br');
+ return;
+ }
+
+ console.log("MeuPerfil: Token encontrado no sessionStorage");
+
+ // Tenta obter o email do usuário logado
+ let userEmail = GlobalFunctions.getUsuarioLogado().email;
+ console.log("MeuPerfil: Email obtido:", userEmail);
+
+ // Se não tem email, tenta fazer refresh do token
+ if (!userEmail) {
+ console.log("MeuPerfil: Email vazio, tentando fazer refresh do token");
+ try {
+ await GlobalFunctions.getToken();
+ // Aguarda um pouco para o refresh ser processado (se necessário)
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Verifica novamente após refresh
+ userEmail = GlobalFunctions.getUsuarioLogado().email;
+ console.log("MeuPerfil: Email após refresh:", userEmail);
+ } catch (error) {
+ console.error("MeuPerfil: Erro ao obter token:", error);
+ }
+ }
+
+ // Se ainda não tem email após tentar refresh, verifica se o token existe
+ // Se o token existe mas não tem email, pode ser problema na decodificação
+ if (!userEmail) {
+ const usuarioData = GlobalFunctions.getUsuarioLogado();
+ console.log("MeuPerfil: Dados do usuário após tentativas:", usuarioData);
+
+ // Se não tem email mas tem token no sessionStorage, tenta carregar mesmo assim
+ // O loadUserProfile vai tratar o erro adequadamente se o email for necessário
+ // Só redireciona se o token estiver completamente inválido (sem UID e sem EID)
+ if (!usuarioData.email && usuarioData.UID === "0" && usuarioData.EID === "0") {
+ console.log("MeuPerfil: Token completamente inválido (sem UID/EID), redirecionando");
+ window.location.replace(import.meta.env.VITE_BASE_URL_HGTX_CORE || 'https://core.hgtx.com.br');
+ return;
+ }
+
+ // Se tem token mas não tem email, pode ser que o email esteja em outro campo
+ // ou o token precisa ser renovado. Tenta carregar o perfil mesmo assim.
+ console.log("MeuPerfil: Token existe mas email vazio, tentando carregar perfil mesmo assim");
+ }
+
+ // Se passou na verificação (ou tem token válido), carrega o perfil
+ console.log("MeuPerfil: Carregando perfil do usuário");
+ loadUserProfile();
+ };
+
+ checkAuthAndLoad();
+ }, []);
+
+ const loadUserProfile = async () => {
+ try {
+ setLoading(true);
+ let userEmail = GlobalFunctions.getUsuarioLogado().email;
+
+ // Se não tem email, tenta obter do Transfer Area como fallback
+ if (!userEmail) {
+ console.log("MeuPerfil: Email não encontrado no token, tentando Transfer Area");
+ const transferEmail = GlobalFunctions.getTransferProperty(
+ TransferAreaProperties.UsuarioEmail
+ );
+ if (transferEmail) {
+ userEmail = transferEmail as string;
+ console.log("MeuPerfil: Email do Transfer Area:", userEmail);
+ }
+ }
+
+ // Se ainda não tem email, tenta obter do token novamente após refresh
+ if (!userEmail) {
+ try {
+ await GlobalFunctions.getToken();
+ await new Promise(resolve => setTimeout(resolve, 500));
+ userEmail = GlobalFunctions.getUsuarioLogado().email;
+ console.log("MeuPerfil: Email após refresh no loadUserProfile:", userEmail);
+ } catch (error) {
+ console.error("MeuPerfil: Erro ao fazer refresh no loadUserProfile:", error);
+ }
+ }
+
+ if (!userEmail) {
+ console.error("MeuPerfil: Não foi possível obter email do usuário");
+ toast({
+ title: "Erro",
+ description: "Email do usuário não encontrado. Por favor, faça login novamente.",
+ variant: "destructive",
+ });
+ setLoading(false);
+ // Não redireciona aqui, deixa o usuário ver o erro
+ return;
+ }
+
+ setEmail(userEmail);
+ const response = await userProfileService.getUserProfile(userEmail);
+
+ if (response.success && response.id) {
+ // Usuário existe
+ setUserExists(true);
+ setUserId(response.id);
+ setNomeCompleto(response.nome);
+ setWhatsapp(formatPhoneNumber(response.whatsapp));
+ setReceberLembretes(response.followup);
+ } else {
+ // Usuário não existe
+ setUserExists(false);
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao carregar perfil:", error);
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ if (errorMessage.includes("não está cadastrado")) {
+ setUserExists(false);
+ } else {
+ toast({
+ title: "Erro",
+ description: errorMessage || "Erro ao carregar perfil do usuário.",
+ variant: "destructive",
+ });
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleWhatsappChange = (e: React.ChangeEvent) => {
+ const formatted = formatPhoneNumber(e.target.value);
+ setWhatsapp(formatted);
+ };
+
+ const handleCreateWhatsappChange = (e: React.ChangeEvent) => {
+ const formatted = formatPhoneNumber(e.target.value);
+ setCreateWhatsapp(formatted);
+ };
+
+ const handleSave = async () => {
+ if (!userExists || !userId) {
+ toast({
+ title: "Erro",
+ description: "Usuário não encontrado. Por favor, crie uma conta primeiro.",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ try {
+ setSaving(true);
+ const response = await userProfileService.updateUser(userId, {
+ nome: nomeCompleto,
+ whatsapp: whatsapp,
+ followup: receberLembretes,
+ });
+
+ if (response.success) {
+ toast({
+ title: "Perfil atualizado",
+ description: "Suas informações foram salvas com sucesso.",
+ });
+ // Atualiza os dados locais
+ setNomeCompleto(response.nome);
+ setWhatsapp(formatPhoneNumber(response.whatsapp));
+ setReceberLembretes(response.followup);
+ } else {
+ throw new Error(response.message || "Erro ao atualizar perfil");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao salvar:", error);
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ toast({
+ title: "Erro ao salvar",
+ description: errorMessage || "Erro ao atualizar perfil. Tente novamente.",
+ variant: "destructive",
+ });
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const handleCreateUser = async () => {
+ if (!createNome.trim()) {
+ toast({
+ title: "Erro",
+ description: "Nome é obrigatório.",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ if (!createWhatsapp.trim()) {
+ toast({
+ title: "Erro",
+ description: "WhatsApp é obrigatório.",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ try {
+ setCreating(true);
+ const response = await userProfileService.createUser({
+ nome: createNome.trim(),
+ email: email,
+ whatsapp: createWhatsapp,
+ followup: createFollowup,
+ });
+
+ if (response.success && response.id) {
+ toast({
+ title: "Conta criada",
+ description: "Sua conta foi criada com sucesso!",
+ });
+ setUserExists(true);
+ setUserId(response.id);
+ setNomeCompleto(response.nome);
+ setWhatsapp(formatPhoneNumber(response.whatsapp));
+ setReceberLembretes(response.followup);
+ setShowCreateModal(false);
+ // Limpa os campos do modal
+ setCreateNome("");
+ setCreateWhatsapp("");
+ setCreateFollowup(true);
+ } else {
+ throw new Error(response.message || "Erro ao criar conta");
+ }
+ } catch (error: unknown) {
+ console.error("Erro ao criar usuário:", error);
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ toast({
+ title: "Erro ao criar conta",
+ description: errorMessage || "Erro ao criar conta. Tente novamente.",
+ variant: "destructive",
+ });
+ } finally {
+ setCreating(false);
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+
Carregando perfil...
+
+
+ );
+ }
+
+ return (
+
+
+
Meu Perfil
+
+ Gerencie suas informações pessoais
+
+
+
+ {!userExists ? (
+
+
+
+
+
+
+ Você ainda não possui uma conta
+
+
+ Crie sua conta para começar a usar o agente pessoal de IA
+
+
+
+
+
+
+ ) : (
+
+
+
+
+ Informações Pessoais
+
+
+
+
+
+ setNomeCompleto(e.target.value)}
+ placeholder="Digite seu nome completo"
+ />
+
+
+
+
+
+
+ O e-mail não pode ser alterado
+
+
+
+
+
+
+
+
+
+ Receba notificações sobre eventos e tarefas
+
+
+
+
+
+
+
+
+ )}
+
+ {/* Modal de Criação de Conta */}
+
+
+ );
+};
+
+export default MeuPerfil;
diff --git a/src/modules/intelligence-ia/pages/NotFound.tsx b/src/modules/intelligence-ia/pages/NotFound.tsx
new file mode 100644
index 0000000..7bc2234
--- /dev/null
+++ b/src/modules/intelligence-ia/pages/NotFound.tsx
@@ -0,0 +1,24 @@
+import { useLocation } from "react-router-dom";
+import { useEffect } from "react";
+
+const NotFound = () => {
+ const location = useLocation();
+
+ useEffect(() => {
+ console.error("404 Error: User attempted to access non-existent route:", location.pathname);
+ }, [location.pathname]);
+
+ return (
+
+ );
+};
+
+export default NotFound;
diff --git a/src/services/asana.ts b/src/services/asana.ts
new file mode 100644
index 0000000..9973158
--- /dev/null
+++ b/src/services/asana.ts
@@ -0,0 +1,435 @@
+/**
+ * Serviço para integração com API do Asana
+ */
+
+/**
+ * Interface para workspace do Asana (resposta da API)
+ */
+export interface AsanaWorkspaceResponse {
+ gid: string;
+ resource_type: string;
+ name: string;
+}
+
+/**
+ * Interface para resposta da API de workspaces
+ */
+export interface AsanaWorkspacesResponse {
+ data: AsanaWorkspaceResponse[];
+}
+
+/**
+ * Interface para workspace formatado (usado no componente)
+ */
+export interface AsanaWorkspace {
+ id: string;
+ name: string;
+}
+
+/**
+ * Interface para usuário do Asana (resposta da API)
+ */
+export interface AsanaUserResponse {
+ gid: string;
+ name: string;
+ resource_type: string;
+}
+
+/**
+ * Interface para resposta da API de usuários
+ */
+export interface AsanaUsersResponse {
+ data: AsanaUserResponse[];
+}
+
+/**
+ * Interface para usuário formatado (usado no componente)
+ */
+export interface AsanaUser {
+ id: string;
+ name: string;
+}
+
+/**
+ * Interface para resposta da API de integração do Asana
+ */
+export interface AsanaIntegrationResponse {
+ success: boolean;
+ integracao_id?: string;
+ id?: string;
+ api_key?: string;
+ workspace_gid?: string;
+ workspace_nome?: string;
+ usuario_asana_gid?: string;
+ usuario_asana_nome?: string;
+}
+
+/**
+ * Interface para request de criação/atualização de integração
+ */
+export interface AsanaIntegrationRequest {
+ user_id?: string;
+ api_key: string;
+ workspace_gid: string;
+ workspace_nome: string;
+ usuario_asana_gid: string;
+ usuario_asana_nome: string;
+}
+
+/**
+ * Serviço para integração com Asana
+ */
+class AsanaService {
+ private readonly BASE_URL = 'https://app.asana.com/api/1.0';
+ private readonly INTEGRATION_BASE_URL = 'https://prod-hgtx-intelligence-n8n.hgtx.com.br';
+ private readonly INTEGRATION_WEBHOOK_ID = 'c898beff-84cb-44df-a69c-6eff27ccd7aa';
+
+ /**
+ * Lista os workspaces do Asana usando o token fornecido
+ *
+ * @param token - Token de acesso do Asana
+ * @returns Promise com a lista de workspaces formatados
+ */
+ async getWorkspaces(token: string): Promise {
+ if (!token || token.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'Token do Asana é obrigatório',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+
+ const headers: Record = {
+ 'accept': 'application/json',
+ 'authorization': `Bearer ${token.trim()}`,
+ };
+
+ const response = await axios.get(
+ `${this.BASE_URL}/workspaces`,
+ { headers }
+ );
+
+ // Converte a resposta da API para o formato esperado pelo componente
+ // A API retorna gid, mas o componente espera id
+ return response.data.data.map((workspace) => ({
+ id: workspace.gid,
+ name: workspace.name,
+ }));
+ } catch (error: unknown) {
+ console.error('Erro ao buscar workspaces do Asana:', error);
+
+ // Retorna o erro da API se existir
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { data?: any; status?: number } };
+
+ if (axiosError.response?.status === 401) {
+ throw {
+ success: false,
+ message: 'Token inválido ou expirado. Verifique sua chave de API.',
+ };
+ }
+
+ if (axiosError.response?.data) {
+ throw {
+ success: false,
+ message: axiosError.response.data.message || 'Erro ao buscar workspaces do Asana',
+ };
+ }
+ }
+
+ throw {
+ success: false,
+ message: error instanceof Error ? error.message : 'Erro ao buscar workspaces do Asana',
+ };
+ }
+ }
+
+ /**
+ * Lista os usuários de um workspace do Asana
+ *
+ * @param token - Token de acesso do Asana
+ * @param workspaceId - ID do workspace (gid)
+ * @returns Promise com a lista de usuários formatados
+ */
+ async getUsers(token: string, workspaceId: string): Promise {
+ if (!token || token.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'Token do Asana é obrigatório',
+ };
+ }
+
+ if (!workspaceId || workspaceId.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'ID do workspace é obrigatório',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+
+ const headers: Record = {
+ 'accept': 'application/json',
+ 'authorization': `Bearer ${token.trim()}`,
+ };
+
+ const response = await axios.get(
+ `${this.BASE_URL}/users?workspace=${workspaceId}`,
+ { headers }
+ );
+
+ // Converte a resposta da API para o formato esperado pelo componente
+ // A API retorna gid, mas o componente espera id
+ return response.data.data.map((user) => ({
+ id: user.gid,
+ name: user.name,
+ }));
+ } catch (error: unknown) {
+ console.error('Erro ao buscar usuários do Asana:', error);
+
+ // Retorna o erro da API se existir
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { data?: any; status?: number } };
+
+ if (axiosError.response?.status === 401) {
+ throw {
+ success: false,
+ message: 'Token inválido ou expirado. Verifique sua chave de API.',
+ };
+ }
+
+ if (axiosError.response?.data) {
+ throw {
+ success: false,
+ message: axiosError.response.data.message || 'Erro ao buscar usuários do Asana',
+ };
+ }
+ }
+
+ throw {
+ success: false,
+ message: error instanceof Error ? error.message : 'Erro ao buscar usuários do Asana',
+ };
+ }
+ }
+
+ /**
+ * Obtém os detalhes da integração do Asana do usuário
+ *
+ * @param userId - ID do usuário
+ * @returns Promise com os detalhes da integração ou null se não houver
+ */
+ async getIntegration(userId: string): Promise {
+ if (!userId || userId.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'ID do usuário é obrigatório',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+ const { GlobalFunctions } = await import('@/GlobalFunctions');
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ 'accept': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ // Em desenvolvimento, usa proxy do Vite para evitar CORS
+ const baseUrl = import.meta.env.DEV
+ ? '/api/intelligence'
+ : this.INTEGRATION_BASE_URL;
+
+ const response = await axios.get(
+ `${baseUrl}/webhook/${this.INTEGRATION_WEBHOOK_ID}/codex/agente-pessoal/integracoes/asana/${userId}`,
+ { headers }
+ );
+
+ // Se success é false, retorna null (não tem integração)
+ if (!response.data.success) {
+ return null;
+ }
+
+ return response.data;
+ } catch (error: unknown) {
+ console.error('Erro ao buscar integração do Asana:', error);
+
+ // Se o erro for 404 ou similar, significa que não tem integração cadastrada
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { status?: number } };
+ if (axiosError.response?.status === 404) {
+ return null;
+ }
+ }
+
+ // Para outros erros, retorna null silenciosamente (não quebra a aplicação)
+ return null;
+ }
+ }
+
+ /**
+ * Cria uma nova integração do Asana
+ *
+ * @param request - Dados da integração a ser criada
+ * @returns Promise com a resposta da criação
+ */
+ async createIntegration(request: AsanaIntegrationRequest): Promise {
+ if (!request.user_id || !request.api_key || !request.workspace_gid || !request.usuario_asana_gid) {
+ throw {
+ success: false,
+ message: 'Dados incompletos para criar integração',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+ const { GlobalFunctions } = await import('@/GlobalFunctions');
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ 'accept': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ // Em desenvolvimento, usa proxy do Vite para evitar CORS
+ const baseUrl = import.meta.env.DEV
+ ? '/api/intelligence'
+ : this.INTEGRATION_BASE_URL;
+
+ const response = await axios.post(
+ `${baseUrl}/webhook/codex/agente-pessoal/integracoes/asana`,
+ request,
+ { headers }
+ );
+
+ return response.data;
+ } catch (error: unknown) {
+ console.error('Erro ao criar integração do Asana:', error);
+
+ // Retorna o erro da API se existir
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { data?: any; status?: number } };
+
+ if (axiosError.response?.data) {
+ const errorData = axiosError.response.data;
+ throw {
+ success: false,
+ message: errorData.message || 'Erro ao criar integração do Asana',
+ };
+ }
+ }
+
+ throw {
+ success: false,
+ message: error instanceof Error ? error.message : 'Erro ao criar integração do Asana',
+ };
+ }
+ }
+
+ /**
+ * Atualiza uma integração existente do Asana
+ *
+ * @param integracaoId - ID da integração a ser atualizada
+ * @param request - Dados da integração a ser atualizada
+ * @returns Promise com a resposta da atualização
+ */
+ async updateIntegration(integracaoId: string, request: Omit): Promise {
+ if (!integracaoId || !request.api_key || !request.workspace_gid || !request.usuario_asana_gid) {
+ throw {
+ success: false,
+ message: 'Dados incompletos para atualizar integração',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+ const { GlobalFunctions } = await import('@/GlobalFunctions');
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ 'accept': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ // Em desenvolvimento, usa proxy do Vite para evitar CORS
+ const baseUrl = import.meta.env.DEV
+ ? '/api/intelligence'
+ : this.INTEGRATION_BASE_URL;
+
+ const response = await axios.put(
+ `${baseUrl}/webhook/${this.INTEGRATION_WEBHOOK_ID}/codex/agente-pessoal/integracoes/asana/${integracaoId}`,
+ request,
+ { headers }
+ );
+
+ return response.data;
+ } catch (error: unknown) {
+ console.error('Erro ao atualizar integração do Asana:', error);
+
+ // Retorna o erro da API se existir
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { data?: any; status?: number } };
+
+ if (axiosError.response?.data) {
+ const errorData = axiosError.response.data;
+ throw {
+ success: false,
+ message: errorData.message || 'Erro ao atualizar integração do Asana',
+ };
+ }
+ }
+
+ throw {
+ success: false,
+ message: error instanceof Error ? error.message : 'Erro ao atualizar integração do Asana',
+ };
+ }
+ }
+}
+
+// Exporta instância única (Singleton)
+export const asanaService = new AsanaService();
diff --git a/src/services/index.ts b/src/services/index.ts
index b0420b1..f1f65a0 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -6,7 +6,28 @@ export { apiService } from './api';
export { transcriptionService } from './transcription';
export { audioGenerationService, VOICE_OPTIONS } from './audioGeneration';
export { imageGenerationService, IMAGE_SIZE_OPTIONS } from './imageGeneration';
+export { userProfileService } from './userProfile';
+export { asanaService } from './asana';
export type { TranscriptionResponse, TranscriptionRequest } from './transcription';
export type { AudioGenerationResponse, AudioGenerationRequest, VoiceType } from './audioGeneration';
export type { ImageGenerationResponse, ImageGenerationRequest, ImageSize } from './imageGeneration';
+export type {
+ UserProfile,
+ CreateUserRequest,
+ UpdateUserRequest,
+ FinancialIndicators,
+ ExpenseItem,
+ ExpensesResponse,
+ ExpensesFilters,
+ ExpenseCategory
+} from './userProfile';
+export type {
+ AsanaWorkspace,
+ AsanaWorkspaceResponse,
+ AsanaWorkspacesResponse,
+ AsanaUser,
+ AsanaUserResponse,
+ AsanaUsersResponse,
+ AsanaIntegrationResponse
+} from './asana';
export type { ApiResponse, UserConfig, ApiError } from './types';
diff --git a/src/services/userProfile.ts b/src/services/userProfile.ts
new file mode 100644
index 0000000..6d4104e
--- /dev/null
+++ b/src/services/userProfile.ts
@@ -0,0 +1,577 @@
+import { apiService } from './api';
+import { GlobalFunctions } from '@/GlobalFunctions';
+
+/**
+ * Interfaces para o serviço de perfil do usuário
+ */
+export interface UserProfile {
+ success: boolean;
+ id?: string;
+ nome: string;
+ email: string;
+ whatsapp: string;
+ followup: boolean;
+ message?: string;
+}
+
+export interface CreateUserRequest {
+ nome: string;
+ email: string;
+ whatsapp: string;
+ followup: boolean;
+}
+
+export interface UpdateUserRequest {
+ nome: string;
+ whatsapp: string;
+ followup: boolean;
+}
+
+/**
+ * Interface para indicadores financeiros
+ */
+export interface FinancialIndicators {
+ success: boolean;
+ id: string;
+ nome: string;
+ email: string;
+ total_despesas: string;
+ total_corporativo: string;
+ total_pessoal: string;
+ message?: string;
+}
+
+/**
+ * Interface para despesa individual da API
+ */
+export interface ExpenseItem {
+ id: string;
+ data_hora: string;
+ descricao: string;
+ tipo: "pessoal" | "corporativo";
+ valor: string;
+ categoria_id: number;
+ categoria_nome: string;
+ usuario_nome: string;
+ usuario_email: string;
+}
+
+/**
+ * Interface para resposta da API de despesas
+ */
+export interface ExpensesResponse {
+ success: boolean;
+ total_registros: number;
+ total_paginas: number;
+ per_page: number;
+ pagina_atual: number;
+ data: ExpenseItem[];
+}
+
+/**
+ * Interface para filtros de busca de despesas
+ */
+export interface ExpensesFilters {
+ page?: number;
+ per_page?: number;
+ descricao?: string;
+ categoria_id?: number;
+ data_inicial?: string;
+ data_final?: string;
+}
+
+/**
+ * Interface para categoria de despesas
+ */
+export interface ExpenseCategory {
+ id: number;
+ nome: string;
+ descricao: string;
+ criado_em: string;
+ atualizado_em: string;
+}
+
+/**
+ * Serviço para gerenciamento de perfil do usuário
+ */
+class UserProfileService {
+ // Em desenvolvimento, usa proxy do Vite para evitar CORS
+ // Em produção, usa URL direta (requer que servidor permita PUT no CORS)
+ private readonly BASE_URL = import.meta.env.DEV
+ ? '/api/intelligence'
+ : 'https://prod-hgtx-intelligence-n8n.hgtx.com.br';
+ private readonly WEBHOOK_ID = '2299acaf-70ee-47e3-a6fc-56dcc678d651';
+ private readonly GET_USER_ENDPOINT = `${this.BASE_URL}/webhook/${this.WEBHOOK_ID}/codex/agente-pessoal/user`;
+ private readonly CREATE_USER_ENDPOINT = `${this.BASE_URL}/webhook/codex/agente-pessoal/user`;
+ private readonly UPDATE_USER_ENDPOINT = `${this.BASE_URL}/webhook/${this.WEBHOOK_ID}/codex/agente-pessoal/user`;
+ private readonly GET_FINANCIAL_INDICATORS_ENDPOINT = `${this.BASE_URL}/webhook/${this.WEBHOOK_ID}/codex/agente-pessoal/financas/indicadores`;
+ private readonly GET_EXPENSES_ENDPOINT = `${this.BASE_URL}/webhook/${this.WEBHOOK_ID}/codex/agente-pessoal/financas`;
+ private readonly GET_CATEGORIES_ENDPOINT = `${this.BASE_URL}/webhook/codex/agente-pessoal/categorias`;
+
+ /**
+ * Obtém os detalhes do usuário pelo email
+ *
+ * @param userEmail - Email do usuário (opcional, usa do GlobalFunctions se não fornecido)
+ * @returns Promise com os dados do usuário ou erro se não existir
+ */
+ async getUserProfile(userEmail?: string): Promise {
+ const email = userEmail || GlobalFunctions.getUsuarioLogado().email;
+
+ if (!email) {
+ throw {
+ success: false,
+ message: 'Email do usuário não fornecido',
+ };
+ }
+
+ try {
+ // Usa axios diretamente pois a URL é absoluta e diferente do baseURL
+ const axios = (await import('axios')).default;
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ // Prepara headers de autenticação
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const response = await axios.get(
+ `${this.GET_USER_ENDPOINT}/${email}`,
+ { headers }
+ );
+
+ return response.data;
+ } catch (error: any) {
+ // Se o usuário não existir, retorna o erro da API
+ if (error.response?.data) {
+ return error.response.data;
+ }
+
+ throw {
+ success: false,
+ message: error.message || 'Erro ao buscar perfil do usuário',
+ status: error.response?.status,
+ };
+ }
+ }
+
+ /**
+ * Cria um novo usuário
+ *
+ * @param request - Dados do usuário a ser criado
+ * @returns Promise com os dados do usuário criado
+ */
+ async createUser(request: CreateUserRequest): Promise {
+ // Validações
+ if (!request.nome || request.nome.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'Nome é obrigatório',
+ };
+ }
+
+ if (!request.email || request.email.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'Email é obrigatório',
+ };
+ }
+
+ if (!request.whatsapp || request.whatsapp.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'WhatsApp é obrigatório',
+ };
+ }
+
+ // Remove formatação do WhatsApp (apenas números)
+ const whatsappNumbers = request.whatsapp.replace(/\D/g, '');
+
+ try {
+ const axios = (await import('axios')).default;
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ // Prepara headers de autenticação
+ // A API pode precisar apenas da apikey, não do token JWT
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ } else {
+ console.warn('VITE_API_KEY não configurada');
+ }
+
+ // Adiciona token JWT se disponível (pode ser necessário)
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ console.log('Criando usuário:', {
+ url: this.CREATE_USER_ENDPOINT,
+ hasApiKey: !!apiKey,
+ hasToken: !!token,
+ data: {
+ nome: request.nome.trim(),
+ email: request.email.trim(),
+ whatsapp: whatsappNumbers,
+ followup: request.followup,
+ },
+ });
+
+ const response = await axios.post(
+ this.CREATE_USER_ENDPOINT,
+ {
+ nome: request.nome.trim(),
+ email: request.email.trim(),
+ whatsapp: whatsappNumbers,
+ followup: request.followup,
+ },
+ { headers }
+ );
+
+ return response.data;
+ } catch (error: any) {
+ // Retorna o erro da API se existir
+ if (error.response?.data) {
+ throw error.response.data;
+ }
+
+ throw {
+ success: false,
+ message: error.message || 'Erro ao criar usuário',
+ status: error.response?.status,
+ };
+ }
+ }
+
+ /**
+ * Atualiza os dados do usuário
+ *
+ * @param userId - ID do usuário
+ * @param request - Dados a serem atualizados
+ * @returns Promise com os dados atualizados do usuário
+ */
+ async updateUser(userId: string, request: UpdateUserRequest): Promise {
+ // Validações
+ if (!userId || userId.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'ID do usuário é obrigatório',
+ };
+ }
+
+ if (!request.nome || request.nome.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'Nome é obrigatório',
+ };
+ }
+
+ if (!request.whatsapp || request.whatsapp.trim().length === 0) {
+ throw {
+ success: false,
+ message: 'WhatsApp é obrigatório',
+ };
+ }
+
+ // Remove formatação do WhatsApp (apenas números)
+ const whatsappNumbers = request.whatsapp.replace(/\D/g, '');
+
+ try {
+ const axios = (await import('axios')).default;
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ // Prepara headers de autenticação
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ console.log('Atualizando usuário:', {
+ url: `${this.UPDATE_USER_ENDPOINT}/${userId}`,
+ method: 'PUT',
+ hasApiKey: !!apiKey,
+ hasToken: !!token,
+ isDev: import.meta.env.DEV,
+ data: {
+ nome: request.nome.trim(),
+ whatsapp: whatsappNumbers,
+ followup: request.followup,
+ },
+ });
+
+ // Usa PUT conforme documentação da API
+ const response = await axios.put(
+ `${this.UPDATE_USER_ENDPOINT}/${userId}`,
+ {
+ nome: request.nome.trim(),
+ whatsapp: whatsappNumbers,
+ followup: request.followup,
+ },
+ { headers }
+ );
+
+ return response.data;
+ } catch (error: any) {
+ // Retorna o erro da API se existir
+ if (error.response?.data) {
+ throw error.response.data;
+ }
+
+ throw {
+ success: false,
+ message: error.message || 'Erro ao atualizar usuário',
+ status: error.response?.status,
+ };
+ }
+ }
+
+ /**
+ * Obtém os indicadores financeiros do usuário
+ *
+ * @param userEmail - Email do usuário (opcional, usa do GlobalFunctions se não fornecido)
+ * @returns Promise com os indicadores financeiros
+ */
+ async getFinancialIndicators(userEmail?: string): Promise {
+ const email = userEmail || GlobalFunctions.getUsuarioLogado().email;
+
+ if (!email) {
+ throw {
+ success: false,
+ message: 'Email do usuário é obrigatório',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ // Prepara headers de autenticação
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const response = await axios.get(
+ `${this.GET_FINANCIAL_INDICATORS_ENDPOINT}/${email}`,
+ { headers }
+ );
+
+ return response.data;
+ } catch (error: unknown) {
+ // Retorna o erro da API se existir
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { data?: FinancialIndicators } };
+ if (axiosError.response?.data) {
+ throw axiosError.response.data;
+ }
+ }
+
+ throw {
+ success: false,
+ message: error instanceof Error ? error.message : 'Erro ao buscar indicadores financeiros',
+ status: error && typeof error === 'object' && 'response' in error
+ ? (error as { response?: { status?: number } }).response?.status
+ : undefined,
+ };
+ }
+ }
+
+ /**
+ * Obtém a lista de despesas do usuário com paginação e filtros
+ *
+ * @param userEmail - Email do usuário (opcional, usa do GlobalFunctions se não fornecido)
+ * @param filters - Filtros de busca (página, itens por página, descrição, categoria, datas)
+ * @returns Promise com a lista de despesas paginada
+ */
+ async getExpenses(userEmail?: string, filters?: ExpensesFilters): Promise {
+ const email = userEmail || GlobalFunctions.getUsuarioLogado().email;
+
+ if (!email) {
+ throw {
+ success: false,
+ message: 'Email do usuário é obrigatório',
+ };
+ }
+
+ try {
+ const axios = (await import('axios')).default;
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ // Prepara headers de autenticação
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ // Constrói query params
+ const params = new URLSearchParams();
+ if (filters?.page) params.append('page', filters.page.toString());
+ if (filters?.per_page) params.append('per_page', filters.per_page.toString());
+ if (filters?.descricao) params.append('descricao', filters.descricao);
+ if (filters?.categoria_id) params.append('categoria_id', filters.categoria_id.toString());
+ if (filters?.data_inicial) params.append('data_inicial', filters.data_inicial);
+ if (filters?.data_final) params.append('data_final', filters.data_final);
+
+ const queryString = params.toString();
+ const url = `${this.GET_EXPENSES_ENDPOINT}/${email}${queryString ? `?${queryString}` : ''}`;
+
+ const response = await axios.get(url, { headers });
+
+ // A API retorna um array com um único objeto
+ if (Array.isArray(response.data) && response.data.length > 0) {
+ const expensesResponse = response.data[0];
+
+ // Valida e limpa o array de dados, removendo objetos vazios
+ if (expensesResponse.data && Array.isArray(expensesResponse.data)) {
+ // Filtra objetos vazios (sem propriedades ou apenas com propriedades vazias)
+ expensesResponse.data = expensesResponse.data.filter((item) => {
+ // Verifica se o objeto tem pelo menos uma propriedade válida
+ return item && typeof item === 'object' && Object.keys(item).length > 0 && item.id;
+ });
+
+ // Se após filtrar não há dados, garante que data seja um array vazio
+ if (expensesResponse.data.length === 0) {
+ expensesResponse.data = [];
+ expensesResponse.total_registros = 0;
+ expensesResponse.total_paginas = 0;
+ }
+ } else {
+ // Se data não é um array válido, inicializa como array vazio
+ expensesResponse.data = [];
+ expensesResponse.total_registros = 0;
+ expensesResponse.total_paginas = 0;
+ }
+
+ return expensesResponse;
+ }
+
+ // Fallback caso a estrutura seja diferente - retorna resposta vazia
+ return {
+ success: true,
+ total_registros: 0,
+ total_paginas: 0,
+ per_page: filters?.per_page || 10,
+ pagina_atual: filters?.page || 1,
+ data: [],
+ };
+ } catch (error: unknown) {
+ // Retorna o erro da API se existir
+ if (error && typeof error === 'object' && 'response' in error) {
+ const axiosError = error as { response?: { data?: ExpensesResponse } };
+ if (axiosError.response?.data) {
+ throw axiosError.response.data;
+ }
+ }
+
+ throw {
+ success: false,
+ message: error instanceof Error ? error.message : 'Erro ao buscar despesas',
+ status: error && typeof error === 'object' && 'response' in error
+ ? (error as { response?: { status?: number } }).response?.status
+ : undefined,
+ };
+ }
+ }
+
+ /**
+ * Obtém a lista de categorias de despesas
+ *
+ * @returns Promise com a lista de categorias
+ */
+ async getCategories(): Promise {
+ try {
+ const axios = (await import('axios')).default;
+
+ // Obtém o token JWT para autenticação
+ const token = await GlobalFunctions.getToken();
+ const apiKey = import.meta.env.VITE_API_KEY || '';
+
+ // Prepara headers de autenticação
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ // Adiciona API key (obrigatória)
+ if (apiKey) {
+ headers['apikey'] = apiKey;
+ }
+
+ // Adiciona token JWT se disponível
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const response = await axios.get(this.GET_CATEGORIES_ENDPOINT, { headers });
+
+ // A API retorna um array de categorias
+ if (Array.isArray(response.data)) {
+ return response.data;
+ }
+
+ return [];
+ } catch (error: unknown) {
+ console.error('Erro ao buscar categorias:', error);
+
+ // Retorna array vazio em caso de erro para não quebrar a aplicação
+ return [];
+ }
+ }
+}
+
+// Exporta instância única (Singleton)
+export const userProfileService = new UserProfileService();
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 254556f..0dfbe2d 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -34,6 +34,14 @@ export default {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
+ success: {
+ DEFAULT: "hsl(var(--success))",
+ foreground: "hsl(var(--success-foreground))",
+ },
+ warning: {
+ DEFAULT: "hsl(var(--warning))",
+ foreground: "hsl(var(--warning-foreground))",
+ },
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
diff --git a/vite.config.ts b/vite.config.ts
index da25c6d..58e4f07 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -8,6 +8,14 @@ export default defineConfig(({ mode }) => ({
server: {
host: "::",
port: 8080,
+ proxy: {
+ '/api/intelligence': {
+ target: 'https://prod-hgtx-intelligence-n8n.hgtx.com.br',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api\/intelligence/, ''),
+ secure: true,
+ },
+ },
},
plugins: [react(), mode === "development" && componentTagger()].filter(Boolean),
resolve: {