Spaces:
Running
Running
| import React, { useState, useEffect } from 'react'; | |
| import { db } from '../../firebase/config'; | |
| import { ref, onValue } from 'firebase/database'; | |
| import { Users, Truck, Heart, Star, Phone, MapPin, History, Search } from 'lucide-react'; | |
| export default function CRMManager() { | |
| const [customers, setCustomers] = useState([]); | |
| const [deliveryOrders, setDeliveryOrders] = useState([]); | |
| const [searchTerm, setSearchTerm] = useState(''); | |
| useEffect(() => { | |
| // Analytics for CRM (from Orders) | |
| onValue(ref(db, 'orders'), (snapshot) => { | |
| const data = snapshot.val(); | |
| if (data) { | |
| const orders = Object.values(data); | |
| const customerMap = {}; | |
| const deliveries = []; | |
| orders.forEach(order => { | |
| if (order.customerEmail) { | |
| const email = order.customerEmail; | |
| if (!customerMap[email]) { | |
| customerMap[email] = { | |
| email, | |
| name: order.customerName || 'Cliente', | |
| totalSpent: 0, | |
| orderCount: 0, | |
| lastOrder: 0 | |
| }; | |
| } | |
| customerMap[email].totalSpent += order.total; | |
| customerMap[email].orderCount += 1; | |
| if (order.timestamp > customerMap[email].lastOrder) { | |
| customerMap[email].lastOrder = order.timestamp; | |
| } | |
| } | |
| if (order.type === 'delivery') { | |
| deliveries.push(order); | |
| } | |
| }); | |
| setCustomers(Object.values(customerMap).sort((a,b) => b.totalSpent - a.totalSpent)); | |
| setDeliveryOrders(deliveries.sort((a,b) => b.timestamp - a.timestamp)); | |
| } | |
| }); | |
| }, []); | |
| const filteredCustomers = customers.filter(c => | |
| c.email.toLowerCase().includes(searchTerm.toLowerCase()) || | |
| c.name.toLowerCase().includes(searchTerm.toLowerCase()) | |
| ); | |
| return ( | |
| <div className="animate-fade-in" style={{ padding: '0 1rem' }}> | |
| <header style={{ marginBottom: '2.5rem' }}> | |
| <h2 className="text-gradient" style={{ fontSize: '2rem', fontWeight: '800', display: 'flex', alignItems: 'center', gap: '0.75rem' }}> | |
| <Users size={28} /> CRM & Fidelización | |
| </h2> | |
| <p style={{ color: 'var(--text-muted)' }}>Gestión de clientes frecuentes y seguimiento de delivery</p> | |
| </header> | |
| <div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '2rem' }}> | |
| {/* Customer List */} | |
| <section className="glass-card"> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}> | |
| <h3 style={sectionTitleStyle}><Heart size={18} /> Clientes Frecuentes</h3> | |
| <div style={{ position: 'relative', width: '200px' }}> | |
| <Search size={14} style={{ position: 'absolute', left: '10px', top: '12px', color: 'var(--text-muted)' }} /> | |
| <input | |
| type="text" placeholder="Buscar..." | |
| value={searchTerm} onChange={e => setSearchTerm(e.target.value)} | |
| style={{ ...inputStyle, paddingLeft: '30px', padding: '0.4rem 0.5rem 0.4rem 2rem', fontSize: '0.8rem' }} | |
| /> | |
| </div> | |
| </div> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', maxHeight: '500px', overflowY: 'auto' }}> | |
| {filteredCustomers.map((c, i) => ( | |
| <div key={c.email} className="glass-panel" style={{ padding: '1rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}> | |
| <div style={{ width: '40px', height: '40px', background: 'rgba(0,0,0,0.03)', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: '800', color: i < 3 ? 'var(--warning)' : 'var(--text-muted)' }}> | |
| {i < 3 ? <Star size={18} fill="var(--warning)" /> : i + 1} | |
| </div> | |
| <div> | |
| <div style={{ fontWeight: '700' }}>{c.name}</div> | |
| <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{c.email}</div> | |
| </div> | |
| </div> | |
| <div style={{ textAlign: 'right' }}> | |
| <div style={{ color: 'var(--success)', fontWeight: '800' }}>${c.totalSpent.toFixed(2)}</div> | |
| <div style={{ fontSize: '0.65rem', color: 'var(--text-muted)' }}>{c.orderCount} visitas | {Math.floor(c.totalSpent / 10)} pts</div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| {/* Delivery Activity */} | |
| <section className="glass-card"> | |
| <h3 style={sectionTitleStyle}><Truck size={18} /> Monitor de Delivery</h3> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> | |
| {deliveryOrders.slice(0, 10).map(order => ( | |
| <div key={order.id} style={{ padding: '1rem', borderLeft: '3px solid var(--primary)', background: 'rgba(0,0,0,0.02)', borderRadius: '0 8px 8px 0' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '0.5rem' }}> | |
| <span style={{ fontWeight: '700', fontSize: '0.9rem' }}>#{order.id.slice(-5)} - {order.customerName}</span> | |
| <span style={{ fontSize: '0.75rem', padding: '2px 8px', borderRadius: '4px', background: 'rgba(255,90,95,0.1)', color: 'var(--primary)' }}>{order.status.toUpperCase()}</span> | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.75rem', color: 'var(--text-muted)' }}> | |
| <MapPin size={12} /> {order.address || 'Local / Para llevar'} | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: '0.25rem' }}> | |
| <History size={12} /> {new Date(order.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} | |
| </div> | |
| </div> | |
| ))} | |
| {deliveryOrders.length === 0 && <p style={{ textAlign: 'center', color: 'var(--text-muted)', paddingTop: '2rem' }}>No hay pedidos delivery recientes</p>} | |
| </div> | |
| </section> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)', outline: 'none' }; | |
| const sectionTitleStyle = { fontSize: '1.2rem', fontWeight: '700', display: 'flex', alignItems: 'center', gap: '0.75rem' }; | |