import { BrowserFrame } from "@/components/Browser/BrowserFrame"; import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { LayoutDashboard, Database, BarChart3, FileText, Plus, Play, Trash2, Settings, Loader2, CheckCircle, XCircle, Clock, Zap, HardDrive, Table, PieChart, LineChart, TrendingUp, Activity, Archive, Download, RefreshCw, Search, Filter, Upload, FileSpreadsheet, GripVertical, ArrowRight, Columns, Layers, FileArchive, MapPin, Send, Eye, Save, Bot, X, Sparkles, MessageSquare, } from "lucide-react"; import { Textarea } from "@/components/ui/textarea"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { BarChart, Bar, LineChart as RechartsLineChart, Line, PieChart as RechartsPieChart, Pie, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell, } from "recharts"; import { SupersetDashboard } from "@/components/SupersetDashboard"; interface BiStats { dataSources: number; datasets: number; charts: number; dashboards: number; backupJobs: number; } interface DataSource { id: number; name: string; type: string; host: string; database: string; isActive: string; lastTestedAt: string | null; createdAt: string; } interface Dataset { id: number; name: string; description: string | null; queryType: string; tableName: string | null; sqlQuery: string | null; createdAt: string; } interface BiChart { id: number; name: string; chartType: string; datasetId: number; createdAt: string; } interface BiDashboard { id: number; name: string; description: string | null; createdAt: string; } interface BackupJob { id: number; name: string; backupType: string; dataSource: { name: string; type: string } | null; isActive: string; createdAt: string; } interface BackupArtifact { id: number; filename: string; fileSize: number | null; status: string; jobName: string; startedAt: string; completedAt: string | null; } interface StagedTable { id: number; name: string; sourceType: string; sourceFile: string | null; tableName: string; columns: string | null; rowCount: number; status: string; targetErp: string | null; description: string | null; createdAt: string; } interface StagingStats { totalTables: number; totalRecords: number; mappedTables: number; pendingMigrations: number; completedMigrations: number; bySourceType: Record; byTargetErp: Record; } interface ErpTarget { id: string; name: string; entities: string[]; } async function fetchStats(): Promise { const res = await fetch("/api/bi/stats", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch stats"); return res.json(); } async function fetchDataSources(): Promise { const res = await fetch("/api/bi/data-sources", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchDatasets(): Promise { const res = await fetch("/api/bi/datasets", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchCharts(): Promise { const res = await fetch("/api/bi/charts", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchDashboards(): Promise { const res = await fetch("/api/bi/dashboards", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchBackupJobs(): Promise { const res = await fetch("/api/bi/backup-jobs", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchBackupArtifacts(): Promise { const res = await fetch("/api/bi/backup-artifacts", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchTables(): Promise { const res = await fetch("/api/bi/tables", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchStagedTables(): Promise { const res = await fetch("/api/staging/tables", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchStagingStats(): Promise { const res = await fetch("/api/staging/stats", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } async function fetchErpTargets(): Promise { const res = await fetch("/api/staging/erp-targets", { credentials: "include" }); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); } function formatBytes(bytes: number | null): string { if (!bytes) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function OverviewTab() { const { data: stats, isLoading } = useQuery({ queryKey: ["bi-stats"], queryFn: fetchStats }); const { data: artifacts = [] } = useQuery({ queryKey: ["backup-artifacts"], queryFn: fetchBackupArtifacts }); if (isLoading) { return
; } return (

{stats?.dataSources || 0}

Fontes de Dados

{stats?.datasets || 0}

Datasets

{stats?.charts || 0}

Gráficos

{stats?.dashboards || 0}

Dashboards

{stats?.backupJobs || 0}

Backups

Atividade Recente
{artifacts.slice(0, 5).map((a) => (
{a.status === "completed" ? ( ) : a.status === "running" ? ( ) : ( )} {a.jobName}
{formatBytes(a.fileSize)}
))} {artifacts.length === 0 && (

Nenhuma atividade recente

)}
Início Rápido
); } interface InternalTable { name: string; columnCount: number; sizeBytes: number; category: string; description: string; } function DataSourcesTab() { const queryClient = useQueryClient(); const { data: sources = [], isLoading } = useQuery({ queryKey: ["data-sources"], queryFn: fetchDataSources }); const { data: internalTables = [] } = useQuery({ queryKey: ["internal-tables"], queryFn: async () => { const res = await fetch("/api/bi/internal-tables", { credentials: "include" }); if (!res.ok) throw new Error("Failed"); return res.json(); }, }); const [showAdd, setShowAdd] = useState(false); const [name, setName] = useState(""); const [type, setType] = useState("postgresql"); const [host, setHost] = useState(""); const [port, setPort] = useState(""); const [database, setDatabase] = useState(""); const [activeSourceType, setActiveSourceType] = useState<"external" | "internal">("internal"); const [searchInternal, setSearchInternal] = useState(""); const [selectedCategory, setSelectedCategory] = useState("all"); const createMutation = useMutation({ mutationFn: async (data: any) => { const res = await fetch("/api/bi/data-sources", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), credentials: "include", }); if (!res.ok) throw new Error("Failed"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["data-sources"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); setShowAdd(false); setName(""); setHost(""); setPort(""); setDatabase(""); }, }); const deleteMutation = useMutation({ mutationFn: async (id: number) => { await fetch(`/api/bi/data-sources/${id}`, { method: "DELETE", credentials: "include" }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["data-sources"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); }, }); const testMutation = useMutation({ mutationFn: async (id: number) => { const res = await fetch(`/api/bi/data-sources/${id}/test`, { method: "POST", credentials: "include" }); if (!res.ok) throw new Error("Failed"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["data-sources"] }); }, }); const createDatasetFromTable = useMutation({ mutationFn: async (tableName: string) => { const res = await fetch(`/api/bi/internal-tables/${tableName}/create-dataset`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: tableName }), credentials: "include", }); if (!res.ok) throw new Error("Failed"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["datasets"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); }, }); const categories = ["all", ...Array.from(new Set(internalTables.map(t => t.category)))]; const filteredTables = internalTables.filter(t => { const matchesSearch = t.name.toLowerCase().includes(searchInternal.toLowerCase()) || t.description.toLowerCase().includes(searchInternal.toLowerCase()); const matchesCategory = selectedCategory === "all" || t.category === selectedCategory; return matchesSearch && matchesCategory; }); if (isLoading) { return
; } return (

Fontes de Dados

Tabelas Internas Nova Fonte de Dados
setName(e.target.value)} className="bg-[#162638] border-white/20 text-white" />
setHost(e.target.value)} className="bg-[#162638] border-white/20 text-white" />
setPort(e.target.value)} type="number" className="bg-[#162638] border-white/20 text-white" />
setDatabase(e.target.value)} className="bg-[#162638] border-white/20 text-white" />
{activeSourceType === "internal" ? (
setSearchInternal(e.target.value)} className="pl-10 bg-[#162638] border-white/20 text-white" />
Repositório de Dados do Sistema ({filteredTables.length} tabelas)

Todos os dados do sistema disponíveis para análise pelo Cientista e Agente Manus

{filteredTables.map((table, idx) => (

{table.name}

{table.description}

{table.category} {table.columnCount} cols
))} ) : ( <> {sources.length === 0 ? (

Nenhuma fonte de dados externa configurada

Clique em "Nova Fonte" para conectar um banco externo

) : (
{sources.map((source) => (

{source.name}

{source.type.toUpperCase()} - {source.host || "localhost"}

{source.isActive === "true" ? "Ativo" : "Inativo"}
))}
)} )} ); } function DatasetCard({ dataset, onDelete }: { dataset: Dataset; onDelete: () => void }) { const [isAnalyzing, setIsAnalyzing] = useState(false); const [analysisResult, setAnalysisResult] = useState(null); const queryClient = useQueryClient(); const runAIAction = async (action: string) => { setIsAnalyzing(true); setAnalysisResult(null); const prompts: Record = { analyze: `[ANÁLISE BI] Dataset: "${dataset.name}" (ID: ${dataset.id}), Tabela: ${dataset.tableName || 'N/A'} INSTRUÇÕES: Use bi_execute_query com datasetId ${dataset.id} para obter os dados. Analise estatísticas descritivas, identifique padrões e gere um resumo executivo com insights.`, chart: `[GRÁFICO BI] Dataset: "${dataset.name}" (ID: ${dataset.id}), Tabela: ${dataset.tableName || 'N/A'} INSTRUÇÕES: Primeiro use bi_execute_query com datasetId ${dataset.id} para obter os dados. Depois use bi_create_chart para criar um gráfico apropriado.`, patterns: `[PADRÕES BI] Dataset: "${dataset.name}" (ID: ${dataset.id}), Tabela: ${dataset.tableName || 'N/A'} INSTRUÇÕES: Use bi_execute_query com datasetId ${dataset.id} para obter os dados. Identifique correlações, tendências e anomalias.`, }; try { const res = await fetch("/api/manus/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: prompts[action] }), credentials: "include", }); if (!res.ok) throw new Error("Falha"); const { runId } = await res.json(); let attempts = 0; while (attempts < 60) { await new Promise(r => setTimeout(r, 1000)); const statusRes = await fetch(`/api/manus/runs/${runId}`, { credentials: "include" }); const run = await statusRes.json(); if (run.status === 'completed' || run.status === 'stopped') { const lastStep = run.steps?.[run.steps.length - 1]; setAnalysisResult(lastStep?.toolOutput || run.result || "Análise concluída."); queryClient.invalidateQueries({ queryKey: ["bi-charts"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); break; } attempts++; } } catch (error) { setAnalysisResult("Erro ao processar. Tente novamente."); } finally { setIsAnalyzing(false); } }; return (
{dataset.queryType === "sql" ? :
}

{dataset.name}

{dataset.queryType === "sql" ? "SQL Query" : dataset.tableName}

{isAnalyzing && (
Analisando dados...
)} {analysisResult && !isAnalyzing && (
Resultado da Análise
{analysisResult}
)} ); } function DataAssistant({ datasets }: { datasets: Dataset[] }) { const [question, setQuestion] = useState(""); const [selectedDatasetId, setSelectedDatasetId] = useState(null); const [messages, setMessages] = useState>([]); const [isLoading, setIsLoading] = useState(false); const queryClient = useQueryClient(); const askQuestion = async () => { if (!question.trim() || !selectedDatasetId) return; const selectedDataset = datasets.find(d => d.id === selectedDatasetId); const userMessage = { type: 'user' as const, content: question }; setMessages(prev => [...prev, userMessage]); setQuestion(""); setIsLoading(true); try { const res = await fetch("/api/bi/ai-analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question, datasetId: selectedDatasetId }), credentials: "include", }); if (!res.ok) { const errData = await res.json().catch(() => ({ error: "Erro desconhecido" })); throw new Error(errData.error || "Falha na análise"); } const data = await res.json(); setMessages(prev => [...prev, { type: 'assistant', content: data.answer || "Análise concluída.", insights: data.insights, suggestedChart: data.suggestedChart, datasetName: data.datasetName, }]); } catch (error: any) { setMessages(prev => [...prev, { type: 'assistant', content: `Desculpe, ocorreu um erro: ${error.message || "Tente novamente."}`, }]); } finally { setIsLoading(false); } }; const createChart = async (suggestedChart: any) => { if (!selectedDatasetId || !suggestedChart) return; try { const res = await fetch("/api/bi/ai-create-chart", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ datasetId: selectedDatasetId, chartConfig: suggestedChart }), credentials: "include", }); if (res.ok) { queryClient.invalidateQueries({ queryKey: ["bi-charts"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); } } catch (error) { console.error("Error creating chart:", error); } }; return ( Assistente de Dados
{messages.length === 0 ? (

Faça uma pergunta sobre seus dados

Ex: "Qual é o total de vendas por cidade?"

) : (
{messages.map((msg, idx) => (

{msg.content}

{msg.insights && msg.insights.length > 0 && (

Insights:

    {msg.insights.map((insight, i) => (
  • {insight}
  • ))}
)} {msg.suggestedChart && (
)}
))} {isLoading && (
Analisando...
)}
)}
setQuestion(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && !e.shiftKey && askQuestion()} className="bg-white/10 border-white/20 text-white placeholder:text-white/40" disabled={!selectedDatasetId || isLoading} />
); } const CHART_COLORS = ['#c89b3c', '#1f334d', '#4caf50', '#2196f3', '#ff9800', '#e91e63', '#9c27b0', '#00bcd4']; function ChartViewer({ chart, datasets }: { chart: BiChart; datasets: Dataset[] }) { const dataset = datasets.find((d: Dataset) => d.id === chart.datasetId); const tableName = dataset?.tableName; const { data: chartData = [], isLoading } = useQuery({ queryKey: ["chart-data", chart.id, tableName], queryFn: async () => { if (!tableName) return []; const res = await fetch(`/api/bi/query`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tableName, limit: 50 }), credentials: "include", }); if (!res.ok) return []; return res.json(); }, enabled: !!tableName, }); const config = chart.config ? JSON.parse(chart.config) : {}; const xAxis = config.xAxis || chart.xAxis || Object.keys(chartData[0] || {})[0]; const yAxis = config.yAxis || chart.yAxis || Object.keys(chartData[0] || {})[1]; if (isLoading) { return (
); } if (!chartData.length) { return (

Sem dados disponíveis

); } const processedData = chartData.map((row: Record) => { const numValue = typeof row[yAxis] === 'string' ? parseFloat(String(row[yAxis]).replace(/[^\d.-]/g, '')) || 0 : Number(row[yAxis]) || 0; return { ...row, [yAxis]: numValue }; }); const renderChart = () => { switch (chart.chartType) { case "bar": return ( ); case "line": return ( ); case "pie": return ( name} > {processedData.map((_: unknown, index: number) => ( ))} ); case "area": return ( ); default: return ( ); } }; return
{renderChart()}
; } function ChartsTab() { const queryClient = useQueryClient(); const { data: charts = [], isLoading } = useQuery({ queryKey: ["bi-charts"], queryFn: fetchCharts }); const { data: datasets = [] } = useQuery({ queryKey: ["datasets"], queryFn: fetchDatasets }); const deleteMutation = useMutation({ mutationFn: async (id: number) => { await fetch(`/api/bi/charts/${id}`, { method: "DELETE", credentials: "include" }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["bi-charts"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); }, }); const getDatasetName = (datasetId: number | null) => { if (!datasetId) return "N/A"; const dataset = datasets.find((d: Dataset) => d.id === datasetId); return dataset?.name || `Dataset #${datasetId}`; }; const getChartIcon = (type: string) => { switch (type) { case "bar": return ; case "line": return ; case "pie": return ; case "area": return ; default: return ; } }; if (isLoading) { return
; } return (

Gráficos Criados

{charts.length} gráficos
{charts.length === 0 ? (

Nenhum gráfico criado ainda.

Use o assistente de IA para criar gráficos automaticamente a partir dos seus dados.

) : (
{charts.map((chart: BiChart) => (
{getChartIcon(chart.chartType)}
{chart.name}

{getDatasetName(chart.datasetId)}

{chart.chartType}

{new Date(chart.createdAt).toLocaleDateString('pt-BR')}

))}
)}
); } function DatasetsTab() { const queryClient = useQueryClient(); const { data: datasets = [], isLoading } = useQuery({ queryKey: ["datasets"], queryFn: fetchDatasets }); const { data: tables = [] } = useQuery({ queryKey: ["tables"], queryFn: fetchTables }); const [showAdd, setShowAdd] = useState(false); const [name, setName] = useState(""); const [queryType, setQueryType] = useState("table"); const [tableName, setTableName] = useState(""); const [sqlQuery, setSqlQuery] = useState(""); const createMutation = useMutation({ mutationFn: async (data: any) => { const res = await fetch("/api/bi/datasets", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), credentials: "include", }); if (!res.ok) throw new Error("Failed"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["datasets"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); setShowAdd(false); setName(""); setTableName(""); setSqlQuery(""); }, }); const deleteMutation = useMutation({ mutationFn: async (id: number) => { await fetch(`/api/bi/datasets/${id}`, { method: "DELETE", credentials: "include" }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["datasets"] }); queryClient.invalidateQueries({ queryKey: ["bi-stats"] }); }, }); if (isLoading) { return
; } return (

Datasets / Consultas

Novo Dataset
setName(e.target.value)} className="bg-[#162638] border-white/20 text-white" />
{queryType === "table" ? (
) : (