import React, { useState, useEffect, useMemo } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut } from 'firebase/auth'; import { getFirestore, collection, addDoc, onSnapshot, doc, updateDoc, deleteDoc, query, where, writeBatch, getDocs } from 'firebase/firestore'; import { LogIn, UserPlus, Users, Car, FileText, DollarSign, LogOut, PlusCircle, Trash2, Edit, X, Search, Bell } from 'lucide-react'; // --- CONFIGURAÇÃO DO FIREBASE --- // Configuração fornecida pelo usuário const firebaseConfig = { apiKey: "AIzaSyB8vzF0ew4FD7ggTuo-OleoUan113JS5ho", authDomain: "despachante-86b79.firebaseapp.com", projectId: "despachante-86b79", storageBucket: "despachante-86b79.appspot.com", messagingSenderId: "1036072046779", appId: "1:1036072046779:web:421f0a33c342166793599e" }; // --- INICIALIZAÇÃO DO FIREBASE --- const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); // --- COMPONENTES DE UI --- const Spinner = () => (
); const Modal = ({ children, isOpen, onClose, title }) => { if (!isOpen) return null; return (
e.stopPropagation()}>

{title}

{children}
); }; const Input = React.forwardRef(({...props}, ref) => ( )); const Select = ({ children, ...props }) => ( ); const Button = ({ children, onClick, className = '', type = 'button', isLoading = false, variant = 'primary' }) => { const baseClasses = 'px-4 py-2 rounded-lg font-semibold transition-all flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed'; const variantClasses = { primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800', secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-2 focus:ring-gray-500', danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-2 focus:ring-red-500', }; return ( ); }; // --- TELA DE AUTENTICAÇÃO --- const AuthScreen = ({ setUser }) => { const [isLogin, setIsLogin] = useState(true); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [isLoading, setIsLoading] = useState(false); const handleAuth = async (e) => { e.preventDefault(); setIsLoading(true); setError(''); try { if (isLogin) { await signInWithEmailAndPassword(auth, email, password); } else { await createUserWithEmailAndPassword(auth, email, password); } // O onAuthStateChanged vai cuidar de atualizar o estado do usuário } catch (err) { setError(err.message.includes("auth/invalid-credential") ? "Credenciais inválidas." : "Erro ao processar. Verifique os dados."); console.error(err); } finally { setIsLoading(false); } }; return (

DespachantePRO

{isLogin ? 'Acesse sua conta' : 'Crie uma nova conta'}

setEmail(e.target.value)} required />
setPassword(e.target.value)} required />
{error &&

{error}

}

{isLogin ? "Não tem uma conta? " : "Já tem uma conta? "}

); }; // --- COMPONENTES PRINCIPAIS DA APLICAÇÃO --- const ClientForm = ({ client, onSave, onCancel }) => { const [formData, setFormData] = useState({ name: '', cpfCnpj: '', phone: '', email: '', address: '' }); useEffect(() => { if (client) { setFormData(client); } else { setFormData({ name: '', cpfCnpj: '', phone: '', email: '', address: '' }); } }, [client]); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); onSave(formData); }; return (
); }; const VehicleForm = ({ vehicle, onSave, onCancel }) => { const [formData, setFormData] = useState({ plate: '', renavam: '', model: '', year: '', color: '' }); useEffect(() => { if (vehicle) { setFormData(vehicle); } else { setFormData({ plate: '', renavam: '', model: '', year: '', color: '' }); } }, [vehicle]); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); onSave(formData); }; return (
); }; const ServiceForm = ({ service, clients, onSave, onCancel }) => { const [formData, setFormData] = useState({ clientId: '', vehicleId: '', serviceType: 'Transferência de Propriedade', status: 'Em andamento', openingDate: new Date().toISOString().split('T')[0], notes: '', totalValue: 0, paidValue: 0, }); const [vehicles, setVehicles] = useState([]); useEffect(() => { if (service) { setFormData({ ...service, totalValue: service.totalValue || 0, paidValue: service.paidValue || 0, }); if (service.clientId) { // Carregar veículos para o cliente selecionado const vehicleCollection = collection(db, `users/${auth.currentUser.uid}/clients/${service.clientId}/vehicles`); onSnapshot(vehicleCollection, (snapshot) => { const vehicleList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setVehicles(vehicleList); }); } } else { setFormData({ clientId: '', vehicleId: '', serviceType: 'Transferência de Propriedade', status: 'Em andamento', openingDate: new Date().toISOString().split('T')[0], notes: '', totalValue: 0, paidValue: 0, }); } }, [service, clients]); const handleClientChange = (e) => { const clientId = e.target.value; setFormData(prev => ({ ...prev, clientId, vehicleId: '' })); if (clientId) { const vehicleCollection = collection(db, `users/${auth.currentUser.uid}/clients/${clientId}/vehicles`); onSnapshot(vehicleCollection, (snapshot) => { const vehicleList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setVehicles(vehicleList); }); } else { setVehicles([]); } }; const handleChange = (e) => { const { name, value, type } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'number' ? parseFloat(value) || 0 : value })); }; const handleSubmit = (e) => { e.preventDefault(); const client = clients.find(c => c.id === formData.clientId); const vehicle = vehicles.find(v => v.id === formData.vehicleId); onSave({ ...formData, clientName: client ? client.name : 'N/A', vehiclePlate: vehicle ? vehicle.plate : 'N/A', }); }; const serviceTypes = ["Transferência de Propriedade", "Primeiro Emplacamento", "Licenciamento Anual", "2ª Via CRV/CRLV", "Comunicação de Venda", "Alteração de Características", "Outro"]; const statusTypes = ["Em andamento", "Pendente de documentos", "Aguardando pagamento", "Protocolado no DETRAN", "Concluído", "Cancelado"]; return (
); }; // --- PÁGINAS DA APLICAÇÃO --- const DashboardPage = ({ clients, services, setCurrentPage }) => { const stats = useMemo(() => { const active = services.filter(s => s.status !== 'Concluído' && s.status !== 'Cancelado').length; const pendingDocs = services.filter(s => s.status === 'Pendente de documentos').length; const concluded = services.filter(s => s.status === 'Concluído').length; const toReceive = services.reduce((acc, s) => acc + ((s.totalValue || 0) - (s.paidValue || 0)), 0); return { active, pendingDocs, concluded, toReceive }; }, [services]); const recentServices = useMemo(() => { return [...services] .sort((a, b) => new Date(b.openingDate) - new Date(a.openingDate)) .slice(0, 5); }, [services]); return (

Painel de Controle

{/* Cards de Estatísticas */}

Serviços Ativos

{stats.active}

Pendentes de Docs

{stats.pendingDocs}

Concluídos

{stats.concluded}

A Receber

R$ {stats.toReceive.toFixed(2)}

{/* Ações Rápidas */}
{/* Lista de Serviços Recentes */}

Serviços Recentes

{recentServices.length > 0 ? recentServices.map(service => ( )) : ( )}
Cliente Placa Tipo Status
{service.clientName} {service.vehiclePlate} {service.serviceType} {service.status}
Nenhum serviço recente.
); }; const ClientsPage = ({ user, clients, services, onSaveClient, onDeleteClient, onSaveVehicle, onDeleteVehicle }) => { const [isClientModalOpen, setIsClientModalOpen] = useState(false); const [isVehicleModalOpen, setIsVehicleModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [editingClient, setEditingClient] = useState(null); const [editingVehicle, setEditingVehicle] = useState(null); const [viewingClient, setViewingClient] = useState(null); const [vehicles, setVehicles] = useState([]); const [clientServices, setClientServices] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const openClientModal = (client = null) => { setEditingClient(client); setIsClientModalOpen(true); }; const closeClientModal = () => { setIsClientModalOpen(false); setEditingClient(null); }; const openVehicleModal = (vehicle = null) => { setEditingVehicle(vehicle); setIsVehicleModalOpen(true); }; const closeVehicleModal = () => { setIsVehicleModalOpen(false); setEditingVehicle(null); }; const openViewModal = (client) => { setViewingClient(client); // Carregar veículos do cliente const vehicleCollection = collection(db, `users/${user.uid}/clients/${client.id}/vehicles`); onSnapshot(vehicleCollection, (snapshot) => { const vehicleList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setVehicles(vehicleList); }); // Filtrar serviços do cliente const relatedServices = services.filter(s => s.clientId === client.id); setClientServices(relatedServices); setIsViewModalOpen(true); }; const closeViewModal = () => { setIsViewModalOpen(false); setViewingClient(null); setVehicles([]); setClientServices([]); }; const handleSaveClient = (data) => { onSaveClient(data, editingClient?.id); closeClientModal(); }; const handleSaveVehicle = (data) => { onSaveVehicle(data, viewingClient.id, editingVehicle?.id); closeVehicleModal(); }; const handleDeleteClient = (clientId) => { if(window.confirm("Tem certeza que deseja excluir este cliente? Todos os seus veículos e serviços associados também serão excluídos.")){ onDeleteClient(clientId); } }; const handleDeleteVehicle = (vehicleId) => { if(window.confirm("Tem certeza que deseja excluir este veículo?")){ onDeleteVehicle(viewingClient.id, vehicleId); } }; const filteredClients = clients.filter(client => client.name.toLowerCase().includes(searchTerm.toLowerCase()) || (client.cpfCnpj && client.cpfCnpj.includes(searchTerm)) ); return (

Clientes

setSearchTerm(e.target.value)} className="pl-10" />
{filteredClients.length > 0 ? filteredClients.map(client => ( )) : ( )}
Nome CPF/CNPJ Telefone Ações
{client.name} {client.cpfCnpj} {client.phone}
Nenhum cliente encontrado.
{viewingClient && (

Informações do Cliente

CPF/CNPJ: {viewingClient.cpfCnpj}

Telefone: {viewingClient.phone}

Email: {viewingClient.email}

Endereço: {viewingClient.address}

Veículos

    {vehicles.length > 0 ? vehicles.map(v => (
  • {v.plate} - {v.model}

    RENAVAM: {v.renavam}

  • )) :

    Nenhum veículo cadastrado.

    }

Histórico de Serviços

    {clientServices.length > 0 ? clientServices.map(s => (
  • {s.serviceType} ({s.vehiclePlate})

    {s.status}

    Data: {new Date(s.openingDate).toLocaleDateString()}

  • )) :

    Nenhum serviço registrado para este cliente.

    }
)}
); }; const getStatusColor = (status) => { switch (status) { case 'Concluído': return 'bg-green-500/20 text-green-300'; case 'Cancelado': return 'bg-red-500/20 text-red-300'; case 'Pendente de documentos': return 'bg-yellow-500/20 text-yellow-300'; case 'Aguardando pagamento': return 'bg-orange-500/20 text-orange-300'; case 'Protocolado no DETRAN': return 'bg-purple-500/20 text-purple-300'; default: return 'bg-blue-500/20 text-blue-300'; // Em andamento } }; const ServicesPage = ({ clients, services, onSaveService, onDeleteService }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [editingService, setEditingService] = useState(null); const [filterStatus, setFilterStatus] = useState('Todos'); const openModal = (service = null) => { setEditingService(service); setIsModalOpen(true); }; const closeModal = () => { setIsModalOpen(false); setEditingService(null); }; const handleSave = (data) => { onSaveService(data, editingService?.id); closeModal(); }; const handleDelete = (serviceId) => { if(window.confirm("Tem certeza que deseja excluir este serviço?")){ onDeleteService(serviceId); } }; const handleStatusChange = async (serviceId, newStatus) => { const serviceRef = doc(db, `users/${auth.currentUser.uid}/services`, serviceId); try { await updateDoc(serviceRef, { status: newStatus }); } catch (error) { console.error("Erro ao atualizar status: ", error); } }; const filteredServices = services.filter(service => filterStatus === 'Todos' || service.status === filterStatus ); const statusTypes = ["Todos", "Em andamento", "Pendente de documentos", "Aguardando pagamento", "Protocolado no DETRAN", "Concluído", "Cancelado"]; return (

Serviços

{filteredServices.length > 0 ? filteredServices.map(service => ( )) : ( )}
Cliente Placa Serviço Data Status Ações
{service.clientName} {service.vehiclePlate} {service.serviceType} {new Date(service.openingDate).toLocaleDateString()}
Nenhum serviço encontrado.
); }; const FinancialPage = ({ services }) => { const [filter, setFilter] = useState('Todos'); const getPaymentStatus = (service) => { const total = service.totalValue || 0; const paid = service.paidValue || 0; if (paid >= total && total > 0) return 'Pago'; if (paid > 0 && paid < total) return 'Parcial'; return 'Pendente'; }; const financialData = useMemo(() => { return services.map(s => ({ ...s, totalValue: s.totalValue || 0, paidValue: s.paidValue || 0, paymentStatus: getPaymentStatus(s), toReceive: (s.totalValue || 0) - (s.paidValue || 0) })); }, [services]); const filteredData = financialData.filter(s => filter === 'Todos' || s.paymentStatus === filter); const totals = useMemo(() => { return filteredData.reduce((acc, s) => { acc.total += s.totalValue; acc.paid += s.paidValue; acc.toReceive += s.toReceive; return acc; }, { total: 0, paid: 0, toReceive: 0 }); }, [filteredData]); const paymentStatusTypes = ["Todos", "Pendente", "Parcial", "Pago"]; const getPaymentStatusColor = (status) => { switch(status) { case 'Pago': return 'bg-green-500/20 text-green-300'; case 'Parcial': return 'bg-blue-500/20 text-blue-300'; case 'Pendente': return 'bg-yellow-500/20 text-yellow-300'; default: return 'bg-gray-500/20 text-gray-300'; } } return (

Financeiro

Faturamento Total

R$ {totals.total.toFixed(2)}

Total Recebido

R$ {totals.paid.toFixed(2)}

Total a Receber

R$ {totals.toReceive.toFixed(2)}

{filteredData.length > 0 ? filteredData.map(service => ( )) : ( )}
Cliente Serviço Valor Total Valor Pago A Receber Status Pag.
{service.clientName} {service.serviceType} R$ {service.totalValue.toFixed(2)} R$ {service.paidValue.toFixed(2)} R$ {service.toReceive.toFixed(2)} {service.paymentStatus}
Nenhum registro financeiro encontrado.
); }; // --- COMPONENTE PRINCIPAL --- export default function App() { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); const [currentPage, setCurrentPage] = useState('dashboard'); const [clients, setClients] = useState([]); const [services, setServices] = useState([]); // --- EFEITO PARA AUTENTICAÇÃO --- useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { setUser(user); setIsLoading(false); }); return () => unsubscribe(); }, []); // --- EFEITOS PARA CARREGAR DADOS DO FIRESTORE --- useEffect(() => { if (!user) { setClients([]); setServices([]); return; } // Carregar Clientes const clientCollection = collection(db, `users/${user.uid}/clients`); const unsubscribeClients = onSnapshot(clientCollection, (snapshot) => { const clientList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setClients(clientList); }); // Carregar Serviços const serviceCollection = collection(db, `users/${user.uid}/services`); const unsubscribeServices = onSnapshot(serviceCollection, (snapshot) => { const serviceList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setServices(serviceList); }); return () => { unsubscribeClients(); unsubscribeServices(); }; }, [user]); // --- FUNÇÕES CRUD --- // Clientes const handleSaveClient = async (data, id) => { const clientCollection = collection(db, `users/${user.uid}/clients`); try { if (id) { const clientRef = doc(db, `users/${user.uid}/clients`, id); await updateDoc(clientRef, data); } else { await addDoc(clientCollection, data); } } catch (error) { console.error("Erro ao salvar cliente: ", error); } }; const handleDeleteClient = async (clientId) => { try { const batch = writeBatch(db); // Deletar o cliente const clientRef = doc(db, `users/${user.uid}/clients`, clientId); batch.delete(clientRef); // Deletar serviços associados const servicesQuery = query(collection(db, `users/${user.uid}/services`), where("clientId", "==", clientId)); const servicesSnapshot = await getDocs(servicesQuery); servicesSnapshot.forEach(doc => batch.delete(doc.ref)); // Deletar veículos (subcoleção) const vehiclesCollectionRef = collection(db, `users/${user.uid}/clients/${clientId}/vehicles`); const vehiclesSnapshot = await getDocs(vehiclesCollectionRef); vehiclesSnapshot.forEach(doc => batch.delete(doc.ref)); await batch.commit(); } catch (error) { console.error("Erro ao deletar cliente e dados associados: ", error); } }; // Veículos const handleSaveVehicle = async (data, clientId, vehicleId) => { const vehicleCollection = collection(db, `users/${user.uid}/clients/${clientId}/vehicles`); try { if (vehicleId) { const vehicleRef = doc(db, `users/${user.uid}/clients/${clientId}/vehicles`, vehicleId); await updateDoc(vehicleRef, data); } else { await addDoc(vehicleCollection, data); } } catch (error) { console.error("Erro ao salvar veículo: ", error); } }; const handleDeleteVehicle = async (clientId, vehicleId) => { try { const vehicleRef = doc(db, `users/${user.uid}/clients/${clientId}/vehicles`, vehicleId); await deleteDoc(vehicleRef); } catch (error) { console.error("Erro ao deletar veículo: ", error); } }; // Serviços const handleSaveService = async (data, id) => { const serviceCollection = collection(db, `users/${user.uid}/services`); try { if (id) { const serviceRef = doc(db, `users/${user.uid}/services`, id); await updateDoc(serviceRef, data); } else { await addDoc(serviceCollection, data); } } catch (error) { console.error("Erro ao salvar serviço: ", error); } }; const handleDeleteService = async (serviceId) => { try { const serviceRef = doc(db, `users/${user.uid}/services`, serviceId); await deleteDoc(serviceRef); } catch (error) { console.error("Erro ao deletar serviço: ", error); } }; const handleLogout = () => { signOut(auth); }; if (isLoading) { return (
); } if (!user) { return ; } const renderPage = () => { switch (currentPage) { case 'clients': return ; case 'services': return ; case 'financial': return ; case 'dashboard': default: return ; } }; const NavItem = ({ icon: Icon, label, page }) => ( ); return (
{/* Sidebar */} {/* Main Content */}
{renderPage()}
); }