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()}>
{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'}
{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
| Cliente |
Placa |
Tipo |
Status |
{recentServices.length > 0 ? recentServices.map(service => (
| {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"
/>
| Nome |
CPF/CNPJ |
Telefone |
Ações |
{filteredClients.length > 0 ? filteredClients.map(client => (
| {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
| Cliente |
Placa |
Serviço |
Data |
Status |
Ações |
{filteredServices.length > 0 ? filteredServices.map(service => (
| {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)}
| Cliente |
Serviço |
Valor Total |
Valor Pago |
A Receber |
Status Pag. |
{filteredData.length > 0 ? filteredData.map(service => (
| {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()}
);
}