import { useState, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { BrowserFrame } from "@/components/Browser/BrowserFrame"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/hooks/use-toast"; import { FileText, Users, TrendingUp, DollarSign, Plus, Search, Edit, Eye, RefreshCw, CheckCircle, XCircle, Clock, Send, ArrowRight, Building2, Loader2, MapPin, Calendar, Target, FileCheck, Rocket, BarChart3 } from "lucide-react"; type TabType = "dashboard" | "customers" | "proposals" | "projects" | "services" | "pipeline"; interface ArcadiaCustomer { name: string; customer_name?: string; territory?: string; customer_group?: string; } interface ArcadiaSalesOrder { name: string; customer: string; customer_name: string; transaction_date: string; delivery_date: string; grand_total: number; status: string; per_delivered: number; per_billed: number; } interface Proposal { id: number; proposalNumber: string; title: string; customerId: string; customerName: string; serviceType: string; status: string; totalValue: number; validUntil: string; createdAt: string; } interface EnvironmentalService { id: number; code?: string; name: string; description?: string; category?: string; basePrice?: string; unit?: string; estimatedDuration?: number; items?: string[]; isActive?: number; } const defaultServices: EnvironmentalService[] = [ { id: -1, code: "MON-001", name: "Monitoramento de Águas Subterrâneas", description: "Campanha de monitoramento de poços com análises laboratoriais", category: "Monitoramento", basePrice: "15000", items: ["Mobilização de equipe", "Coleta de amostras", "Análises laboratoriais", "Relatório técnico"] }, { id: -2, code: "INV-001", name: "Investigação Confirmatória", description: "Investigação de áreas potencialmente contaminadas conforme CONAMA 420", category: "Investigação", basePrice: "45000", items: ["Sondagens", "Instalação de poços", "Coleta de amostras", "Análises laboratoriais", "Modelo conceitual", "Relatório técnico"] }, { id: -3, code: "INV-002", name: "Investigação Detalhada", description: "Delimitação de plumas de contaminação e avaliação de risco", category: "Investigação", basePrice: "85000", items: ["Sondagens adicionais", "Poços multinível", "Slug tests", "Análises químicas", "Modelagem", "Avaliação de risco", "Relatório técnico"] }, { id: -4, code: "REM-001", name: "Projeto de Remediação", description: "Elaboração de plano de intervenção para áreas contaminadas", category: "Remediação", basePrice: "35000", items: ["Análise de alternativas", "Dimensionamento", "Projeto executivo", "Cronograma", "Orçamento"] }, { id: -5, code: "LIC-001", name: "Licenciamento Ambiental", description: "Elaboração de estudos para licenciamento ambiental", category: "Licenciamento", basePrice: "25000", items: ["Diagnóstico ambiental", "Estudos específicos", "Elaboração de EIA/RIMA ou RAS", "Acompanhamento CETESB"] }, { id: -6, code: "AUD-001", name: "Auditoria Ambiental (Due Diligence)", description: "Avaliação ambiental de imóveis para transações imobiliárias", category: "Consultoria", basePrice: "18000", items: ["Pesquisa documental", "Vistoria técnica", "Avaliação de passivos", "Relatório de due diligence"] }, ]; const serviceCategories = ["Monitoramento", "Investigação", "Remediação", "Licenciamento", "Consultoria", "Outro"]; const statusColors: Record = { rascunho: "bg-gray-500", enviada: "bg-blue-500", visualizada: "bg-purple-500", em_negociacao: "bg-yellow-500", aceita: "bg-green-500", rejeitada: "bg-red-500", expirada: "bg-gray-400", Completed: "bg-green-500", "To Deliver and Bill": "bg-yellow-500", "To Bill": "bg-blue-500", "To Deliver": "bg-orange-500", Draft: "bg-gray-500", }; const api = { get: async (url: string) => { const res = await fetch(url, { credentials: "include" }); if (!res.ok) throw new Error("Request failed"); return res.json(); }, post: async (url: string, data: any) => { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), credentials: "include", }); if (!res.ok) throw new Error("Request failed"); return res.json(); }, put: async (url: string, data: any) => { const res = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), credentials: "include", }); if (!res.ok) throw new Error("Request failed"); return res.json(); }, delete: async (url: string) => { const res = await fetch(url, { method: "DELETE", credentials: "include" }); if (!res.ok) throw new Error("Request failed"); return res.json(); }, }; export default function CommercialEnv() { const { toast } = useToast(); const queryClient = useQueryClient(); const [activeTab, setActiveTab] = useState("dashboard"); const [searchTerm, setSearchTerm] = useState(""); const [showProposalDialog, setShowProposalDialog] = useState(false); const [showServiceDialog, setShowServiceDialog] = useState(false); const [selectedService, setSelectedService] = useState(null); const [editingService, setEditingService] = useState(null); const [selectedCustomer, setSelectedCustomer] = useState(""); const [newItems, setNewItems] = useState(""); const [showProjectDialog, setShowProjectDialog] = useState(false); const [selectedOrder, setSelectedOrder] = useState(null); const [projectWorkflow, setProjectWorkflow] = useState<"pre_projeto" | "backlog" | "planejamento" | "execucao">("pre_projeto"); interface ProjectStatus { orderId: string; stage: "venda" | "pre_projeto" | "backlog_tecnico" | "planejamento" | "em_execucao"; technicalTeam?: string; plannedStart?: string; plannedEnd?: string; responsiblePerson?: string; notes?: string; } const [projectStatuses, setProjectStatuses] = useState>({}); const { data: erpCustomers = [], isLoading: loadingCustomers, refetch: refetchCustomers } = useQuery({ queryKey: ["/api/erpnext/customers"], queryFn: async () => { const res = await api.get("/api/erpnext/resource/Customer?limit=100"); return res.data || []; }, }); const { data: salesOrders = [], isLoading: loadingOrders, refetch: refetchOrders } = useQuery({ queryKey: ["/api/erpnext/sales-orders"], queryFn: async () => { const res = await api.get("/api/erpnext/resource/Sales%20Order?limit=50"); if (!res.data) return []; const orders = await Promise.all( res.data.slice(0, 10).map(async (o: any) => { try { const detail = await api.get(`/api/erpnext/resource/Sales%20Order/${o.name}`); return detail.data; } catch { return null; } }) ); return orders.filter(Boolean); }, }); const { data: proposals = [], refetch: refetchProposals } = useQuery({ queryKey: ["/api/crm/proposals"], queryFn: async () => { try { const res = await api.get("/api/crm/proposals"); return res || []; } catch { return []; } }, }); const createProposalMutation = useMutation({ mutationFn: async (data: any) => { return api.post("/api/crm/proposals", data); }, onSuccess: () => { toast({ title: "Proposta criada com sucesso" }); refetchProposals(); setShowProposalDialog(false); }, onError: () => toast({ title: "Erro ao criar proposta", variant: "destructive" }), }); const { data: dbServices = [], isLoading: loadingServices, refetch: refetchServices } = useQuery({ queryKey: ["/api/quality/services"], queryFn: async () => { try { const res = await api.get("/api/quality/services"); return res.data || []; } catch { return []; } }, }); const allServices = dbServices.length > 0 ? dbServices : defaultServices; const createServiceMutation = useMutation({ mutationFn: async (data: any) => api.post("/api/quality/services", data), onSuccess: () => { toast({ title: "Serviço criado com sucesso" }); refetchServices(); setShowServiceDialog(false); setEditingService(null); }, onError: () => toast({ title: "Erro ao criar serviço", variant: "destructive" }), }); const updateServiceMutation = useMutation({ mutationFn: async ({ id, data }: { id: number; data: any }) => api.put(`/api/quality/services/${id}`, data), onSuccess: () => { toast({ title: "Serviço atualizado com sucesso" }); refetchServices(); setShowServiceDialog(false); setEditingService(null); }, onError: () => toast({ title: "Erro ao atualizar serviço", variant: "destructive" }), }); const deleteServiceMutation = useMutation({ mutationFn: async (id: number) => api.delete(`/api/quality/services/${id}`), onSuccess: () => { toast({ title: "Serviço excluído com sucesso" }); refetchServices(); }, onError: () => toast({ title: "Erro ao excluir serviço", variant: "destructive" }), }); const handleSaveService = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const itemsArray = newItems.split("\n").filter(i => i.trim()); const data = { code: formData.get("code") as string, name: formData.get("name") as string, description: formData.get("description") as string, category: formData.get("category") as string, basePrice: formData.get("basePrice") as string, unit: formData.get("unit") as string, estimatedDuration: Number(formData.get("estimatedDuration")) || null, items: itemsArray.length > 0 ? itemsArray : null, isActive: 1, }; if (editingService && editingService.id > 0) { updateServiceMutation.mutate({ id: editingService.id, data }); } else { createServiceMutation.mutate(data); } }; const openEditService = (service: EnvironmentalService) => { setEditingService(service); setNewItems(service.items?.join("\n") || ""); setShowServiceDialog(true); }; const openNewService = () => { setEditingService(null); setNewItems(""); setShowServiceDialog(true); }; const getStatusBadge = (status: string) => { const color = statusColors[status] || "bg-gray-500"; const label = status.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()); return {label}; }; const filteredCustomers = erpCustomers.filter(c => c.name?.toLowerCase().includes(searchTerm.toLowerCase()) || c.customer_name?.toLowerCase().includes(searchTerm.toLowerCase()) ); const totalPipeline = salesOrders.reduce((sum, o) => sum + (o.grand_total || 0), 0); const completedOrders = salesOrders.filter(o => o.status === "Completed").length; const pendingOrders = salesOrders.filter(o => o.status !== "Completed").length; return (

Comercial - Engenharia Ambiental

Propostas, clientes e pipeline de vendas integrado ao Arcádia SOE

setActiveTab(v as TabType)}> Dashboard Clientes Propostas Pedidos/Projetos Serviços Pipeline
Clientes Arcádia
{erpCustomers.length}

Total cadastrado

Pedidos Ativos
{pendingOrders}

Em andamento

Pedidos Concluídos
{completedOrders}

Finalizados

Pipeline Total
{totalPipeline.toLocaleString("pt-BR", { style: "currency", currency: "BRL" })}

Valor em pedidos

Últimos Pedidos Pedido Cliente Valor Status {salesOrders.slice(0, 5).map((order) => ( {order.name} {order.customer_name} {order.grand_total?.toLocaleString("pt-BR", { style: "currency", currency: "BRL" })} {getStatusBadge(order.status)} ))} {salesOrders.length === 0 && ( Nenhum pedido encontrado )}
Serviços Mais Vendidos
{allServices.slice(0, 4).map((service) => (

{service.name}

{service.category}

{service.basePrice.toLocaleString("pt-BR", { style: "currency", currency: "BRL" })}
))}
setSearchTerm(e.target.value)} className="w-64" data-testid="search-customers" />
{erpCustomers.length} clientes
{loadingCustomers ? (
) : ( Código Nome Território Grupo Ações {filteredCustomers.map((customer) => ( {customer.name} {customer.customer_name || customer.name} {customer.territory || "-"} {customer.customer_group || "-"} ))} {filteredCustomers.length === 0 && ( Nenhum cliente encontrado )}
)}

Propostas Comerciais

Número Título Cliente Serviço Valor Status Ações {proposals.map((p: any) => ( {p.proposalNumber || `PROP-${p.id}`} {p.title} {p.customerName || "-"} {p.serviceType || "-"} {(p.totalValue || 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL" })} {getStatusBadge(p.status || "rascunho")} ))} {proposals.length === 0 && ( Nenhuma proposta cadastrada. Clique em "Nova Proposta" para começar. )}

Pedidos de Venda → Projetos

Gerencie o fluxo: Venda → Pré-Projeto → Backlog Técnico → Planejamento → Execução

{salesOrders.length}

Vendas

{Object.values(projectStatuses).filter(p => p.stage === "pre_projeto").length}

Pré-Projetos

{Object.values(projectStatuses).filter(p => p.stage === "backlog_tecnico").length}

Backlog Técnico

{Object.values(projectStatuses).filter(p => p.stage === "planejamento").length}

Em Planejamento

{Object.values(projectStatuses).filter(p => p.stage === "em_execucao").length}

Em Execução

{loadingOrders ? (
) : ( Pedido Cliente Valor Total Etapa do Projeto Entrega Status ERP Ações {salesOrders.map((order) => { const projectStatus = projectStatuses[order.name]; return ( {order.name} {order.customer_name} {order.grand_total?.toLocaleString("pt-BR", { style: "currency", currency: "BRL" })} {!projectStatus && Venda} {projectStatus?.stage === "pre_projeto" && Pré-Projeto} {projectStatus?.stage === "backlog_tecnico" && Backlog Técnico} {projectStatus?.stage === "planejamento" && Planejamento} {projectStatus?.stage === "em_execucao" && Em Execução}
{order.per_delivered || 0}%
{getStatusBadge(order.status)} ); })} {salesOrders.length === 0 && ( Nenhum pedido encontrado. Sincronize com o Arcádia SOE. )}
)}

Catálogo de Serviços - Engenharia Ambiental

{allServices.map((service) => (
{service.category} {service.basePrice && ( {Number(service.basePrice).toLocaleString("pt-BR", { style: "currency", currency: "BRL" })} )}
{service.name} {service.description}
{service.items && service.items.length > 0 && ( <>

Itens inclusos:

    {service.items.map((item, i) => (
  • {item}
  • ))}
)}
{service.id > 0 && ( )}
))}

Pipeline de Vendas

Prospecção
0

R$ 0,00

Proposta Enviada
{proposals.filter((p: any) => p.status === "enviada").length}

Em negociação

Em Execução
{pendingOrders}

{salesOrders.filter(o => o.status !== "Completed").reduce((s, o) => s + o.grand_total, 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL" })}

Concluído
{completedOrders}

{salesOrders.filter(o => o.status === "Completed").reduce((s, o) => s + o.grand_total, 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL" })}

Fluxo: Proposta → Projeto Workflow de conversão comercial para Engenharia Ambiental
Proposta
Negociação
Aprovação
Pedido
Projeto
Nova Proposta Comercial
{ e.preventDefault(); const formData = new FormData(e.currentTarget); createProposalMutation.mutate({ title: formData.get("title") as string, opportunityId: null, status: "rascunho", validUntil: formData.get("validUntil") as string, totalValue: selectedService?.basePrice || Number(formData.get("totalValue")) || 0, notes: formData.get("notes") as string, }); }}>
{selectedService && ( {selectedService.name}

{selectedService.description}

{selectedService.items && selectedService.items.length > 0 && (
{selectedService.items.map((item, i) => ( {item} ))}
)} {selectedService.basePrice && (

Valor base: {Number(selectedService.basePrice).toLocaleString("pt-BR", { style: "currency", currency: "BRL" })}

)}
)}