'use client' import { useState, useEffect, useCallback } from 'react' import ProtectedRoute from '@/components/ProtectedRoute' import Modal from '@/components/Modal' import LoadingSpinner from '@/components/LoadingSpinner' import Link from 'next/link' import { toast } from 'react-hot-toast' import { Users, Plus, Search, Filter, Mail, Phone, Building2, Star, Edit, Trash2, Eye, Download, Upload, ArrowLeft, UserPlus, Briefcase, Tag, X, Loader2 } from 'lucide-react' import { contactsAPI, Contact, CreateContactData, UpdateContactData, ContactFilters } from '@/lib/api/contacts' import { categoriesAPI, Category } from '@/lib/api/categories' import ContactForm from '@/components/contacts/ContactForm' import ContactImport from '@/components/contacts/ContactImport' function flattenCategories(cats: Category[], result: Category[] = []): Category[] { for (const c of cats) { result.push(c) if (c.children?.length) flattenCategories(c.children, result) } return result } function ContactsContent() { const [contacts, setContacts] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [selectedContacts, setSelectedContacts] = useState>(new Set()) const [showBulkActions, setShowBulkActions] = useState(false) const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const [total, setTotal] = useState(0) const pageSize = 10 const [searchTerm, setSearchTerm] = useState('') const [selectedType, setSelectedType] = useState('all') const [selectedSpecialization, setSelectedSpecialization] = useState('all') const [createDefaultType, setCreateDefaultType] = useState('INDIVIDUAL') const [selectedStatus, setSelectedStatus] = useState('all') const [selectedSource, setSelectedSource] = useState('all') const [selectedRating, setSelectedRating] = useState('all') const [selectedCategory, setSelectedCategory] = useState('all') const [categories, setCategories] = useState([]) const [showAdvancedFilters, setShowAdvancedFilters] = useState(false) const [showCreateModal, setShowCreateModal] = useState(false) const [showEditModal, setShowEditModal] = useState(false) const [showDeleteDialog, setShowDeleteDialog] = useState(false) const [showExportModal, setShowExportModal] = useState(false) const [showImportModal, setShowImportModal] = useState(false) const [selectedContact, setSelectedContact] = useState(null) const [submitting, setSubmitting] = useState(false) const [exporting, setExporting] = useState(false) const [exportExcludeCompanyEmployees, setExportExcludeCompanyEmployees] = useState(false) const fetchContacts = useCallback(async () => { setLoading(true) setError(null) try { const filters: ContactFilters = { page: currentPage, pageSize, } if (searchTerm) filters.search = searchTerm if (selectedType !== 'all') filters.type = selectedType if (selectedSpecialization !== 'all') filters.specialization = selectedSpecialization if (selectedStatus !== 'all') filters.status = selectedStatus if (selectedSource !== 'all') filters.source = selectedSource if (selectedRating !== 'all') filters.rating = parseInt(selectedRating) if (selectedCategory !== 'all') filters.category = selectedCategory const data = await contactsAPI.getAll(filters) setContacts(data.contacts) setTotal(data.total) setTotalPages(data.totalPages) } catch (err: any) { setError(err.response?.data?.message || 'Failed to load contacts') toast.error('Failed to load contacts') } finally { setLoading(false) } }, [currentPage, searchTerm, selectedType, selectedStatus, selectedSource, selectedRating, selectedCategory, selectedSpecialization]) useEffect(() => { const debounce = setTimeout(() => { setCurrentPage(1) fetchContacts() }, 500) return () => clearTimeout(debounce) }, [searchTerm]) useEffect(() => { fetchContacts() }, [currentPage, selectedType, selectedStatus, selectedSource, selectedRating, selectedCategory, selectedSpecialization]) const handleCreate = async (data: CreateContactData) => { setSubmitting(true) try { await contactsAPI.create(data) toast.success('Contact created successfully!') setShowCreateModal(false) resetForm() fetchContacts() } catch (err: any) { const message = err.response?.data?.message || 'Failed to create contact' toast.error(message) throw err } finally { setSubmitting(false) } } const handleEdit = async (data: UpdateContactData) => { if (!selectedContact) return setSubmitting(true) try { await contactsAPI.update(selectedContact.id, data) toast.success('Contact updated successfully!') setShowEditModal(false) resetForm() fetchContacts() } catch (err: any) { const message = err.response?.data?.message || 'Failed to update contact' toast.error(message) throw err } finally { setSubmitting(false) } } const handleDelete = async () => { if (!selectedContact) return setSubmitting(true) try { await contactsAPI.archive(selectedContact.id, 'Deleted by user') toast.success('Contact deleted successfully!') setShowDeleteDialog(false) setSelectedContact(null) fetchContacts() } catch (err: any) { const message = err.response?.data?.message || 'Failed to delete contact' toast.error(message) } finally { setSubmitting(false) } } const resetForm = () => { setSelectedContact(null) } const openEditModal = (contact: Contact) => { setSelectedContact(contact) setShowEditModal(true) } const openDeleteDialog = (contact: Contact) => { setSelectedContact(contact) setShowDeleteDialog(true) } const getTypeColor = (type: string) => { const colors: Record = { INDIVIDUAL: 'bg-blue-100 text-blue-700', COMPANY: 'bg-green-100 text-green-700', HOLDING: 'bg-purple-100 text-purple-700', GOVERNMENT: 'bg-orange-100 text-orange-700', ORGANIZATION: 'bg-cyan-100 text-cyan-700', EMBASSIES: 'bg-red-100 text-red-700', BANK: 'bg-emerald-100 text-emerald-700', UNIVERSITY: 'bg-indigo-100 text-indigo-700', SCHOOL: 'bg-yellow-100 text-yellow-700', UN: 'bg-sky-100 text-sky-700', NGO: 'bg-pink-100 text-pink-700', INSTITUTION: 'bg-gray-100 text-gray-700' } return colors[type] || 'bg-gray-100 text-gray-700' } useEffect(() => { categoriesAPI.getTree().then(setCategories).catch(() => {}) }, []) const getTypeLabel = (type: string) => { const labels: Record = { INDIVIDUAL: 'فرد', COMPANY: 'شركة', HOLDING: 'مجموعة', GOVERNMENT: 'حكومي', ORGANIZATION: 'منظمات', EMBASSIES: 'سفارات', BANK: 'بنوك', UNIVERSITY: 'جامعات', SCHOOL: 'مدارس', UN: 'UN', NGO: 'NGO', INSTITUTION: 'مؤسسة' } return labels[type] || type } const organizationTypes = new Set([ 'COMPANY', 'HOLDING', 'GOVERNMENT', 'ORGANIZATION', 'EMBASSIES', 'BANK', 'UNIVERSITY', 'SCHOOL', 'UN', 'NGO', 'INSTITUTION', ]) const isOrganizationContact = (contact: Contact) => organizationTypes.has(contact.type) const getListContactName = (contact: Contact) => { return contact.name || '-' } const getListCompanyName = (contact: Contact) => { return contact.companyName || '-' } const getListContactNameAr = (contact: Contact) => { return (contact as any).nameAr || '' } const supplierSpecializations = [ 'كاميرات', 'شبكات', 'أجهزة كومبيوتر', 'projectors', 'مقاسم هاتفية', ' Mobile - Tablet', 'firewall', 'طاقة بديلة', 'حديد', ' باركود - POS', 'أجهزة منزلية', 'تكييف وتبريد', ] return (

إدارة جهات الاتصال

Contact Management

{selectedContacts.size > 0 && (
{selectedContacts.size} selected
)}

Total Contacts

{total}

Active Individuals

{contacts.filter(c => c.type === 'INDIVIDUAL' && c.status === 'ACTIVE').length}

Companies

{contacts.filter(c => c.type === 'COMPANY').length}

This Page

{contacts.length}

setSearchTerm(e.target.value)} className="w-full pr-10 pl-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900" />
{showAdvancedFilters && (
)}
{loading ? (
) : error ? (

{error}

) : contacts.length === 0 ? (

No contacts found

) : ( <>
{contacts.map((contact) => { const isSelected = selectedContacts.has(contact.id) return ( ) })}
0 && contacts.every(c => selectedContacts.has(c.id))} onChange={(e) => { if (e.target.checked) { setSelectedContacts(new Set(contacts.map(c => c.id))) } else { setSelectedContacts(new Set()) } }} className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" /> Company Contact Info Contact Type Status Actions
{ const newSelected = new Set(selectedContacts) if (e.target.checked) { newSelected.add(contact.id) } else { newSelected.delete(contact.id) } setSelectedContacts(newSelected) }} className="rounded border-gray-300 text-blue-600 focus:ring-blue-500" /> {getListCompanyName(contact) !== '-' ? (
{getListCompanyName(contact).charAt(0).toUpperCase()}

{getListCompanyName(contact)}

) : ( - )}
{contact.email && (
{contact.email}
)} {(contact.phone || contact.mobile) && (
{contact.phone || contact.mobile}
)}

{getListContactName(contact)}

{getListContactNameAr(contact) && (

{getListContactNameAr(contact)}

)}
{getTypeLabel(contact.type)} {contact.status === 'ACTIVE' ? 'Active' : 'Inactive'}

Showing {((currentPage - 1) * pageSize) + 1} to{' '} {Math.min(currentPage * pageSize, total)} of{' '} {total} contacts

{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { const page = i + 1 return ( ) })} {totalPages > 5 && ...}
)}
{ setShowCreateModal(false) resetForm() }} title="Create New Contact" size="xl" > { await handleCreate(data as CreateContactData) }} onCancel={() => { setShowCreateModal(false) resetForm() }} submitting={submitting} /> { setShowEditModal(false) resetForm() }} title="Edit Contact" size="xl" > { await handleEdit(data as UpdateContactData) }} onCancel={() => { setShowEditModal(false) resetForm() }} submitting={submitting} /> {showExportModal && (
setShowExportModal(false)} />

Export Contacts

Download contacts data

Export {total} contacts matching current filters

Format: Excel (.xlsx)

)} {showDeleteDialog && selectedContact && (
setShowDeleteDialog(false)} />

Delete Contact

This action cannot be undone

Are you sure you want to delete {selectedContact.name}?

)} {showImportModal && ( setShowImportModal(false)} onSuccess={() => { setShowImportModal(false) fetchContacts() }} /> )}
) } export default function ContactsPage() { return ( ) }