import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Customer, User } from '../types'; import { useCustomers, useCreateCustomer } from '../hooks/useCustomers'; import { Search, Plus, MoreHorizontal, Filter, ArrowUpDown, Mail, Phone, X, Eye } from 'lucide-react'; import Portal from '../components/Portal'; interface CustomersProps { onMasquerade: (user: User) => void; effectiveUser: User; } const Customers: React.FC = ({ onMasquerade, effectiveUser }) => { const { t } = useTranslation(); const [searchTerm, setSearchTerm] = useState(''); const [sortConfig, setSortConfig] = useState<{ key: keyof Customer; direction: 'asc' | 'desc' }>({ key: 'name', direction: 'asc' }); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [formData, setFormData] = useState({ name: '', email: '', phone: '', tags: '', city: '', state: '', zip: '' }); const { data: customers = [], isLoading, error } = useCustomers(); const createCustomerMutation = useCreateCustomer(); const handleSort = (key: keyof Customer) => { setSortConfig(current => ({ key, direction: current.key === key && current.direction === 'asc' ? 'desc' : 'asc', })); }; const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleAddCustomer = (e: React.FormEvent) => { e.preventDefault(); const newCustomer: Partial = { phone: formData.phone, city: formData.city, state: formData.state, zip: formData.zip, status: 'Active', tags: formData.tags.split(',').map(t => t.trim()).filter(t => t.length > 0) }; createCustomerMutation.mutate(newCustomer); setIsAddModalOpen(false); setFormData({ name: '', email: '', phone: '', tags: '', city: '', state: '', zip: '' }); }; const filteredCustomers = useMemo(() => { let sorted = [...customers]; if (searchTerm) { const lowerTerm = searchTerm.toLowerCase(); sorted = sorted.filter(c => c.name.toLowerCase().includes(lowerTerm) || c.email.toLowerCase().includes(lowerTerm) || c.phone.includes(searchTerm) ); } sorted.sort((a, b) => { const aValue = a[sortConfig.key]; const bValue = b[sortConfig.key]; if (aValue === null || aValue === undefined) return 1; if (bValue === null || bValue === undefined) return -1; if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1; if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1; return 0; }); return sorted; }, [customers, searchTerm, sortConfig]); // Only owners can masquerade as customers (per backend permissions) const canMasquerade = effectiveUser.role === 'owner'; if (isLoading) { return (
); } if (error) { return (

{t('customers.errorLoading')}: {(error as Error).message}

); } return (

{t('customers.title')}

{t('customers.description')}

setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white border border-gray-200 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent transition-colors duration-200 placeholder-gray-400 dark:placeholder-gray-500" />
{filteredCustomers.map((customer: any) => { const customerUser = customer.user_data; return ( ); })}
handleSort('name')}>
{t('customers.customer')}
{t('customers.contactInfo')} handleSort('status')}>
{t('customers.status')}
handleSort('totalSpend')}>
{t('customers.totalSpend')}
handleSort('lastVisit')}>
{t('customers.lastVisit')}
{t('common.actions')}
{customer.avatarUrl ? {customer.name} : {customer.name.substring(0, 2).toUpperCase()}}
{customer.name}
{customer.tags && (
{customer.tags.map(tag => ({tag}))}
)}
{customer.email}
{customer.phone}
{customer.status} ${customer.totalSpend.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {customer.lastVisit ? customer.lastVisit.toLocaleDateString() : {t('customers.never')}}
{canMasquerade && customerUser && ( )}
{filteredCustomers.length === 0 && (

{t('customers.noCustomersFound')}

)}
{isAddModalOpen && (

{t('customers.addNewCustomer')}

)}
); }; export default Customers;